From 752b3bf14cd3463b07cbeee541b67c3ac2ac41f5 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Wed, 16 Aug 2023 15:04:02 -0500 Subject: [PATCH 001/137] Initial commit --- services/content-watcher/LICENSE | 201 +++++++++++++++++++++++++++++ services/content-watcher/README.md | 2 + 2 files changed, 203 insertions(+) create mode 100644 services/content-watcher/LICENSE create mode 100644 services/content-watcher/README.md diff --git a/services/content-watcher/LICENSE b/services/content-watcher/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/services/content-watcher/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md new file mode 100644 index 00000000..728b40be --- /dev/null +++ b/services/content-watcher/README.md @@ -0,0 +1,2 @@ +# content-publishing-service +A microservice to publish DSNP content to frequency From 8d46c85a59e81209e524dddb265d78d5e3eaf7ee Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Mon, 21 Aug 2023 13:15:24 -0500 Subject: [PATCH 002/137] Basic Scaffolding (#1) * base nestjs for content publishing * fix build * feedback --- services/content-watcher/.eslintrc.json | 64 + .../.github/workflows/build.yml | 59 + .../common/is-full-release/action.yml | 22 + .../.github/workflows/release.yml | 99 + services/content-watcher/.gitignore | 5 + services/content-watcher/.prettierrc | 10 + services/content-watcher/.tool-versions | 1 + services/content-watcher/Dockerfile | 32 + services/content-watcher/INSTALLING.md | 72 + services/content-watcher/README.md | 5 +- services/content-watcher/dev.Dockerfile | 6 + .../content-watcher/docker-compose.dev.yaml | 38 + services/content-watcher/env.template | 18 + services/content-watcher/jest.config.js | 7 + services/content-watcher/package-lock.json | 9649 +++++++++++++++++ services/content-watcher/package.json | 86 + .../src/blockchain/blockchain-constants.ts | 29 + .../src/blockchain/blockchain.module.ts | 15 + .../src/blockchain/blockchain.service.ts | 138 + .../src/blockchain/create-keys.ts | 15 + .../src/blockchain/event-error.ts | 34 + .../src/blockchain/extrinsic.ts | 116 + .../src/config/config.module.ts | 12 + .../src/config/config.service.spec.ts | 251 + .../src/config/config.service.ts | 91 + .../content-watcher/src/config/env.config.ts | 64 + .../content-publishing-service.controller.ts | 18 + .../src/content-publishing-service.module.ts | 51 + .../src/development.controller.ts | 21 + .../interfaces/capacity-limit.interface.ts | 4 + services/content-watcher/src/main.ts | 38 + services/content-watcher/tsconfig.json | 38 + 32 files changed, 11106 insertions(+), 2 deletions(-) create mode 100644 services/content-watcher/.eslintrc.json create mode 100644 services/content-watcher/.github/workflows/build.yml create mode 100644 services/content-watcher/.github/workflows/common/is-full-release/action.yml create mode 100644 services/content-watcher/.github/workflows/release.yml create mode 100644 services/content-watcher/.gitignore create mode 100644 services/content-watcher/.prettierrc create mode 100644 services/content-watcher/.tool-versions create mode 100644 services/content-watcher/Dockerfile create mode 100644 services/content-watcher/INSTALLING.md create mode 100644 services/content-watcher/dev.Dockerfile create mode 100644 services/content-watcher/docker-compose.dev.yaml create mode 100644 services/content-watcher/env.template create mode 100644 services/content-watcher/jest.config.js create mode 100644 services/content-watcher/package-lock.json create mode 100644 services/content-watcher/package.json create mode 100644 services/content-watcher/src/blockchain/blockchain-constants.ts create mode 100644 services/content-watcher/src/blockchain/blockchain.module.ts create mode 100644 services/content-watcher/src/blockchain/blockchain.service.ts create mode 100644 services/content-watcher/src/blockchain/create-keys.ts create mode 100644 services/content-watcher/src/blockchain/event-error.ts create mode 100644 services/content-watcher/src/blockchain/extrinsic.ts create mode 100644 services/content-watcher/src/config/config.module.ts create mode 100644 services/content-watcher/src/config/config.service.spec.ts create mode 100644 services/content-watcher/src/config/config.service.ts create mode 100644 services/content-watcher/src/config/env.config.ts create mode 100644 services/content-watcher/src/content-publishing-service.controller.ts create mode 100644 services/content-watcher/src/content-publishing-service.module.ts create mode 100644 services/content-watcher/src/development.controller.ts create mode 100644 services/content-watcher/src/interfaces/capacity-limit.interface.ts create mode 100644 services/content-watcher/src/main.ts create mode 100644 services/content-watcher/tsconfig.json diff --git a/services/content-watcher/.eslintrc.json b/services/content-watcher/.eslintrc.json new file mode 100644 index 00000000..98fe9ce2 --- /dev/null +++ b/services/content-watcher/.eslintrc.json @@ -0,0 +1,64 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "airbnb-base", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json", + "sourceType": "module" + }, + "settings": { + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "never", + "jsx": "never", + "ts": "never", + "tsx": "never" + } + ], + "import/parsers": { + "@typescript-eslint/parser": [".ts", ".tsx"] + }, + "import/resolver": { + "typescript": { + "directory": "./tsconfig.json" + }, + "node": { + "extensions": [".js", ".jsx", ".ts", ".d.ts", ".tsx"] + } + }, + "react": { + "version": "999.99.99" + } + }, + "rules": { + "no-console": "off", + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "never", + "jsx": "never", + "ts": "never", + "tsx": "never" + } + ], + "import/no-unresolved": [2, { "commonjs": true, "amd": true }], + "import/named": 2, + "import/namespace": 2, + "import/default": 2, + "import/export": 2, + "import/prefer-default-export": "off", + "indent": "off", + "no-unused-vars": "off", + "prettier/prettier": 2 + }, + "plugins": ["import", "prettier"] +} diff --git a/services/content-watcher/.github/workflows/build.yml b/services/content-watcher/.github/workflows/build.yml new file mode 100644 index 00000000..2a456a84 --- /dev/null +++ b/services/content-watcher/.github/workflows/build.yml @@ -0,0 +1,59 @@ +name: Build And Test ContentPublishing Service +concurrency: + group: ${{github.workflow}}-${{github.ref}} + cancel-in-progress: true +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + build_Nest_js: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + cache-dependency-path: package-lock.json + - name: Install dependencies + run: npm ci + - name: Build Nest.js + run: npm run build + test_jest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + cache-dependency-path: package-lock.json + - name: Install dependencies + run: npm ci + - name: Run Jest + run: npm run test + check_licenses: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + cache-dependency-path: package-lock.json + - name: Install dependencies + run: npm ci + - name: License Check + # List all the licenses and error out if it is not one of the supported licenses + run: npx license-report --fields=name --fields=licenseType | jq 'map(select(.licenseType | IN("MIT", "Apache-2.0", "ISC", "BSD-3-Clause", "BSD-2-Clause") | not)) | if length == 0 then halt else halt_error(1) end' diff --git a/services/content-watcher/.github/workflows/common/is-full-release/action.yml b/services/content-watcher/.github/workflows/common/is-full-release/action.yml new file mode 100644 index 00000000..9e17770c --- /dev/null +++ b/services/content-watcher/.github/workflows/common/is-full-release/action.yml @@ -0,0 +1,22 @@ +name: Is Full Release? +description: Determines whether the version tag represents a full release +inputs: + version-tag: + description: "Version tag in v#.#.#[-*] format" + required: true +outputs: + is-full-release: + description: "'true' if full release, 'false' otherwise" + value: ${{steps.is-full-release.outputs.is_full_release}} +runs: + using: "composite" + steps: + - name: Full Release? + id: is-full-release + shell: bash + run: | + version_tag=${{inputs.version-tag}} + is_full_release=$([[ "$version_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] && \ + echo 'true' || echo 'false') + echo "is_full_release: $is_full_release" + echo "is_full_release=$is_full_release" >> $GITHUB_OUTPUT diff --git a/services/content-watcher/.github/workflows/release.yml b/services/content-watcher/.github/workflows/release.yml new file mode 100644 index 00000000..3f2f7c90 --- /dev/null +++ b/services/content-watcher/.github/workflows/release.yml @@ -0,0 +1,99 @@ +name: Release +run-name: Cut Release ${{github.event.inputs.release-version || github.ref_name}} +concurrency: + group: ${{github.workflow}}-${{github.ref}} + cancel-in-progress: true +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' # ex. v1.0.0 + - 'v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+' # ex. v1.1.0-rc1 + - 'v0.0.1' # used for testing only + - 'v0.0.1-rc[0-9]+' # used for testing only + workflow_dispatch: + inputs: + release-version: + description: 'Release version (v#.#.#[-rc#])' + required: true + +env: + NEW_RELEASE_TAG_FROM_UI: ${{github.event.inputs.release-version}} + TEST_RUN: ${{startsWith(github.event.inputs.release-version || github.ref_name, 'v0.0.1')}} + DOCKER_HUB_PROFILE: amplicalabs + IMAGE_NAME: content-publishing-service + +jobs: + build-and-publish-container-image: + name: Build and publish container image + runs-on: ubuntu-20.04 + container: ghcr.io/libertydsnp/frequency/ci-base-image + steps: + - name: Validate Version Tag + if: env.NEW_RELEASE_TAG_FROM_UI != '' + shell: bash + run: | + version=${{env.NEW_RELEASE_TAG_FROM_UI}} + echo "Release version entered in UI: $version" + regex='^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-rc[1-9]\d*)?$' + if [[ ! $version =~ $regex ]]; then + echo "ERROR: Entered version $version is not valid." + echo "Please use v#.#.#[-rc#] format." + exit 1 + fi + echo "valid-version=true" >> $GITHUB_OUTPUT + - name: Check Out Repo + uses: actions/checkout@v3 + with: + ref: ${{env.NEW_RELEASE_TAG_FROM_UI}} + - name: Set up tags for standalone image + id: standalone-tags + uses: docker/metadata-action@v4 + with: + flavor: | + latest=auto + prefix=standalone-,onlatest=true + images: | + ${{env.DOCKER_HUB_PROFILE}}/${{env.IMAGE_NAME}} + tags: | + type=semver,pattern={{version}} + - name: Set up tags for app-only image + id: app-only-tags + uses: docker/metadata-action@v4 + with: + flavor: | + latest=auto + prefix=apponly-,onlatest=true + images: | + ${{env.DOCKER_HUB_PROFILE}}/${{env.IMAGE_NAME}} + tags: | + type=semver,pattern={{version}} + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + with: + platforms: | + linux/amd64 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{secrets.DOCKERHUB_USERNAME_FC}} + password: ${{secrets.DOCKERHUB_TOKEN_FC}} + - name: Build and Push Standalone (Complete) Container Image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64 + push: ${{env.TEST_RUN != 'true'}} + file: ./Dockerfile + target: standalone + tags: ${{ steps.standalone-tags.outputs.tags }} + - name: Build and Push App-Only Container Image + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64 + push: ${{env.TEST_RUN != 'true'}} + file: ./Dockerfile + target: app-only + tags: ${{ steps.app-only-tags.outputs.tags }} diff --git a/services/content-watcher/.gitignore b/services/content-watcher/.gitignore new file mode 100644 index 00000000..b1b73f10 --- /dev/null +++ b/services/content-watcher/.gitignore @@ -0,0 +1,5 @@ +node_modules +dist +.env* +.vscode +coverage diff --git a/services/content-watcher/.prettierrc b/services/content-watcher/.prettierrc new file mode 100644 index 00000000..8a1a0966 --- /dev/null +++ b/services/content-watcher/.prettierrc @@ -0,0 +1,10 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 180, + "tabWidth": 2, + "useTabs": false +} diff --git a/services/content-watcher/.tool-versions b/services/content-watcher/.tool-versions new file mode 100644 index 00000000..8ead549e --- /dev/null +++ b/services/content-watcher/.tool-versions @@ -0,0 +1 @@ +nodejs 18.16.0 diff --git a/services/content-watcher/Dockerfile b/services/content-watcher/Dockerfile new file mode 100644 index 00000000..3135bdae --- /dev/null +++ b/services/content-watcher/Dockerfile @@ -0,0 +1,32 @@ +FROM --platform=linux/amd64 node:18 as build + +# TODO: The deployment docker image should install the content publishing +# service from NPM rather than building from source +WORKDIR /app +COPY package*.json ./ +RUN npm install + +# Build / Copy the rest of the application files to the container and build +COPY . . +RUN npm run build + +FROM build as app-only + +EXPOSE 3000 + +ENTRYPOINT npm start + +FROM build as standalone + +# Install Redis on top of the base image +RUN apt-get -y update +RUN apt-get -y install redis +RUN sed -e 's/^appendonly .*$/appendonly yes/' /etc/redis/redis.conf > /etc/redis/redis.conf.appendonly +RUN mv /etc/redis/redis.conf.appendonly /etc/redis/redis.conf + +ENV REDIS_URL=redis://localhost:6379 + +VOLUME [ "/var/lib/redis" ] + +# Start the application +ENTRYPOINT service redis-server start && npm start diff --git a/services/content-watcher/INSTALLING.md b/services/content-watcher/INSTALLING.md new file mode 100644 index 00000000..4112867f --- /dev/null +++ b/services/content-watcher/INSTALLING.md @@ -0,0 +1,72 @@ +# INSTALLING + +## A Note about Redis Persistence + +The application requires a Redis server that is configured with `Append-only file` persistence. This is so that application state can be maintained across Redis restarts. Notes on how to configure this are included below for each type of deployment. + + +## Deploying using prebuilt Docker images + +### Standalone (complete) image + +The standalone container image is meant to be a complete solution for a provider. It contains a single instance of the main application, plus a pre-configured Redis server. Simply download the latest [container image](https://hub.docker.com/r/amplicalabs/content-publishing-service/) and deploy using your favorite container management system. +``` + docker pull amplicalabs/content-publishing-service:standalone-latest +``` + +The internal Redis server included in the complete image is already configured for persistence; it is simply necessary to configure your container pod to map the directory `/var/lib/redis` to a persistent storage volume. + +#### Note: The internal redis server runs as user:group 100:102, so mapped volume permissions must at minimum allow write access to this user. How this is provisioned will depend on the specifics of your persistent storage infrastructure. If this is not configured properly, the redis server will fail to start, and the application upon launch will throw `ECONNREFUSED` errors. + +Follow the instructions below for [configuration](#configuration), with the exception that you should _not_ modify `REDIS_URL`, as it already points to the internal Redis server. + +### App-only image + +The app-only image is meant to be used for providers who would rather utilize a Redis instance in their own (or their cloud infrastructure provider's) external Redis instance or service. To download the latest [container image](https://hub.docker.com/r/amplicalabs/content-publishing-service/), simply: +``` + docker pull amplicalabs/content-publishing-service:apponly-latest +``` +In this case, you need to ensure that the following settings are configured in your Redis instance: +``` +appendonly true +dir +appendonlydir +``` + +You must also minimally map `appendonlydir` (or the entire `dir`) to a persistent storage volume in your infrastructure environment + +## Building and Deploying the Application + +If you choose to build & deploy the application yourself, simply install the prerequisites: +* NodeJS 18 + +Note that, at present, due to limitaions in the `@dsnp/graph-sdk` module, the applcation can only be run on a `linux/amd64` platform. Support for other platforms is contingent on building & installing the `@dsnp/graph-sdk` module from source, which is outside the scope of this document. + +To build the application: +``` + npm run build +``` + +To run the application: +``` + npm start +``` + +## Configuration + +For the application to start & run correctly, it is necessary to configure the environment with certain parameters. These should be injected into a container pod if running in a containerized environment. + +The following is a list of environment variables that may be set to control the application's behavior and environment. The complete list can always be referenced [here](./env.template) + +|Variable|required?|Description|Default| +|-|-|-|-| +|`FREQUENCY_URL`|**yes**|Blockchain URL|_none_| +|`PROVIDER_ID`|**yes**|MSA ID of provider|_none_| +|`PROVIDER_BASE_URL`|**yes**|URL of provider graph query endpoint|_none_| +|`PROVIDER_ACCESS_TOKEN`|no|Optional access token to be used with requests to provider graph query endpoint|_none_| +|`PROVIDER_ACCOUNT_SEED_PHRASE`|**yes**|Seed phrase for provider control keypair|_none_| +|`REDIS_URL`|**yes**|URL used to connect to Redis instance|_none_
\*preset to the internal Redis URL in the standalone container| +|`BLOCKCHAIN_SCAN_INTERVAL_MINUTES`|no|# of minutes to wait in between scans of the blockchain|180| +|`QUEUE_HIGH_WATER`|no|# of pending graph scan queue entries to allow before pausing blockchain scanning until the next scan cycle|1000| +|`GRAPH_ENVIRONMENT_TYPE`|**yes**|Indicates which blockchain network to connect to.
Possible values:
* `Mainnet`
* `Rococo`
* `Dev`|_none_| +|`GRAPH_ENVIRONMENT_DEV_CONFIG`|no
*required for 'Dev' Graph Environment|JSON configuration object for GraphSDK configuration. Used to test against a local development Frequency node|_none_| diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index 728b40be..cfcb7194 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -1,2 +1,3 @@ -# content-publishing-service -A microservice to publish DSNP content to frequency +# Content Publisher + +A microservice to publish DSNP content to frequency. diff --git a/services/content-watcher/dev.Dockerfile b/services/content-watcher/dev.Dockerfile new file mode 100644 index 00000000..75173082 --- /dev/null +++ b/services/content-watcher/dev.Dockerfile @@ -0,0 +1,6 @@ +FROM node:18-alpine3.17 + +WORKDIR /app + +# Start the application +CMD ["npm", "run", "start:dev:docker"] diff --git a/services/content-watcher/docker-compose.dev.yaml b/services/content-watcher/docker-compose.dev.yaml new file mode 100644 index 00000000..f00d6080 --- /dev/null +++ b/services/content-watcher/docker-compose.dev.yaml @@ -0,0 +1,38 @@ +version: '3' + +services: + content-publishing-service: + build: + context: . + dockerfile: dev.Dockerfile + ports: + - 3000:3000 + env_file: + - .env.docker.dev + volumes: + - ./:/app + networks: + - content-publishing-service-network-dev + + redis: + image: redis:latest + ports: + - 6379:6379 + networks: + - content-publishing-service-network-dev + volumes: + - redis_data:/data + + frequency: + image: frequencychain/instant-seal-node:latest + ports: + - 9944:9944 + networks: + - content-publishing-service-network-dev + container_name: frequency-node + +volumes: + redis_data: + +networks: + content-publishing-service-network-dev: diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template new file mode 100644 index 00000000..410c1410 --- /dev/null +++ b/services/content-watcher/env.template @@ -0,0 +1,18 @@ +# Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development +FREQUENCY_URL=ws://0.0.0.0:9944 +PROVIDER_ID=1 +PROVIDER_BASE_URL=https://some-provider/api/v1.0.0 +REDIS_URL=redis://0.0.0.0:6379 +PROVIDER_ACCESS_TOKEN=some-token +BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 +QUEUE_HIGH_WATER=1000 +PROVIDER_ACCOUNT_SEED_PHRASE='come finish flower cinnamon blame year glad tank domain hunt release fatigue' +WEBHOOK_FAILURE_THRESHOLD=3 +HEALTH_CHECK_SUCCESS_THRESHOLD=10 +WEBHOOK_RETRY_INTERVAL_SECONDS=10 +HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 +HEALTH_CHECK_MAX_RETRIES=4 +CAPACITY_LIMIT='{"type":"percentage", "value":80}' + +# An optional bearer token may be specified for provider authentication +PROVIDER_ACCESS_TOKEN=some-token diff --git a/services/content-watcher/jest.config.js b/services/content-watcher/jest.config.js new file mode 100644 index 00000000..7eb5e95a --- /dev/null +++ b/services/content-watcher/jest.config.js @@ -0,0 +1,7 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + setupFiles: ["dotenv/config"], + testPathIgnorePatterns: ['/dist'] +}; diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json new file mode 100644 index 00000000..1f9f486e --- /dev/null +++ b/services/content-watcher/package-lock.json @@ -0,0 +1,9649 @@ +{ + "name": "content-publishing-service", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "content-publishing-service", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@dsnp/graph-sdk": "^0.0.11", + "@frequency-chain/api-augment": "1.7.0", + "@liaoliaots/nestjs-redis": "^9.0.5", + "@nestjs/axios": "^2.0.0", + "@nestjs/bullmq": "^10.0.0", + "@nestjs/common": "^9.4.0", + "@nestjs/config": "^2.3.1", + "@nestjs/core": "^9.4.0", + "@nestjs/event-emitter": "^1.4.1", + "@nestjs/platform-express": "^9.4.0", + "@nestjs/schedule": "^3.0.1", + "@nestjs/testing": "^9.4.0", + "@nestjs/typeorm": "^9.0.1", + "@polkadot/api": "^10.9.1", + "@polkadot/api-base": "^10.9.1", + "@polkadot/keyring": "^12.3.2", + "@polkadot/types": "^10.9.1", + "@polkadot/util": "^12.3.2", + "@polkadot/util-crypto": "^12.3.2", + "axios": "^1.3.6", + "bullmq": "^3.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "ioredis": "^5.3.2", + "joi": "^17.9.1", + "rxjs": "^7.8.1", + "time-constants": "^1.0.3" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@polkadot/typegen": "10.9.1", + "@types/jest": "^29.5.2", + "@types/time-constants": "^1.0.0", + "@typescript-eslint/parser": "^5.59.8", + "@typescript-eslint/typescript-estree": "5.59.8", + "dotenv": "^16.3.1", + "eslint": "^8.42.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.8.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-nestjs": "^1.2.3", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-promise": "^6.1.1", + "license-report": "^6.4.0", + "ts-jest": "^29.1.0", + "ts-node": "^10.9.1", + "ts-node-dev": "^2.0.0", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-module-transforms": "^7.22.5", + "@babel/helpers": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "1.9.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.22.3", + "license": "MIT", + "peer": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@dsnp/graph-sdk": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@dsnp/graph-sdk/-/graph-sdk-0.0.11.tgz", + "integrity": "sha512-GQJ+gnXLku1HyyVJfHM4CgMtv0GAi00jhNVnPlmhe5bMwkfj6Pg1ZCS8/fKgRiu8G5PAVGGCSInA/2iyBZC33g==", + "engines": { + "node": "^14.0.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "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.5.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "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/js": { + "version": "8.42.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@frequency-chain/api-augment": { + "version": "1.7.0", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/api": "^10.7.3", + "@polkadot/rpc-provider": "^10.7.3", + "@polkadot/types": "^10.7.3" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "license": "MIT" + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jest/schemas": { + "version": "29.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.25.16" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.4.3", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jest/test-result": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/transform/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jest/types": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@kessler/tableify": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@liaoliaots/nestjs-redis": { + "version": "9.0.5", + "license": "MIT", + "dependencies": { + "tslib": "2.4.1" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0", + "ioredis": "^5.0.0" + } + }, + "node_modules/@liaoliaots/nestjs-redis/node_modules/tslib": { + "version": "2.4.1", + "license": "0BSD" + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@nestjs/axios": { + "version": "2.0.0", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "axios": "^1.3.1", + "reflect-metadata": "^0.1.12", + "rxjs": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/@nestjs/bull-shared": { + "version": "10.0.0", + "license": "MIT", + "dependencies": { + "tslib": "2.5.3" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/@nestjs/bullmq": { + "version": "10.0.0", + "license": "MIT", + "dependencies": { + "@nestjs/bull-shared": "^10.0.0", + "tslib": "2.5.3" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "bullmq": "^3.0.0" + } + }, + "node_modules/@nestjs/common": { + "version": "9.4.3", + "license": "MIT", + "dependencies": { + "iterare": "1.2.1", + "tslib": "2.5.3", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "cache-manager": "<=5", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "cache-manager": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/config": { + "version": "2.3.3", + "license": "MIT", + "dependencies": { + "dotenv": "16.1.4", + "dotenv-expand": "10.0.0", + "lodash": "4.17.21", + "uuid": "9.0.0" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^6.0.0 || ^7.2.0" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "16.1.4", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/@nestjs/core": { + "version": "9.4.3", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@nuxtjs/opencollective": "0.3.2", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "3.2.0", + "tslib": "2.5.3", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/microservices": "^9.0.0", + "@nestjs/platform-express": "^9.0.0", + "@nestjs/websockets": "^9.0.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/event-emitter": { + "version": "1.4.2", + "license": "MIT", + "dependencies": { + "eventemitter2": "6.4.9" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0", + "reflect-metadata": "^0.1.12" + } + }, + "node_modules/@nestjs/platform-express": { + "version": "9.4.3", + "license": "MIT", + "dependencies": { + "body-parser": "1.20.2", + "cors": "2.8.5", + "express": "4.18.2", + "multer": "1.4.4-lts.1", + "tslib": "2.5.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0" + } + }, + "node_modules/@nestjs/schedule": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "cron": "2.3.1", + "uuid": "9.0.0" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.12" + } + }, + "node_modules/@nestjs/testing": { + "version": "9.4.3", + "license": "MIT", + "dependencies": { + "tslib": "2.5.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^9.0.0", + "@nestjs/core": "^9.0.0", + "@nestjs/microservices": "^9.0.0", + "@nestjs/platform-express": "^9.0.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + } + } + }, + "node_modules/@nestjs/typeorm": { + "version": "9.0.1", + "license": "MIT", + "dependencies": { + "uuid": "8.3.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, + "node_modules/@nestjs/typeorm/node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@noble/curves": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.1", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nuxtjs/opencollective": { + "version": "0.3.2", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/@nuxtjs/opencollective/node_modules/node-fetch": { + "version": "2.6.11", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@pkgr/utils": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.2.12", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@polkadot/api": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/api-augment": "10.9.1", + "@polkadot/api-base": "10.9.1", + "@polkadot/api-derive": "10.9.1", + "@polkadot/keyring": "^12.3.1", + "@polkadot/rpc-augment": "10.9.1", + "@polkadot/rpc-core": "10.9.1", + "@polkadot/rpc-provider": "10.9.1", + "@polkadot/types": "10.9.1", + "@polkadot/types-augment": "10.9.1", + "@polkadot/types-codec": "10.9.1", + "@polkadot/types-create": "10.9.1", + "@polkadot/types-known": "10.9.1", + "@polkadot/util": "^12.3.1", + "@polkadot/util-crypto": "^12.3.1", + "eventemitter3": "^5.0.1", + "rxjs": "^7.8.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/api-augment": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/api-base": "10.9.1", + "@polkadot/rpc-augment": "10.9.1", + "@polkadot/types": "10.9.1", + "@polkadot/types-augment": "10.9.1", + "@polkadot/types-codec": "10.9.1", + "@polkadot/util": "^12.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/api-base": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/rpc-core": "10.9.1", + "@polkadot/types": "10.9.1", + "@polkadot/util": "^12.3.1", + "rxjs": "^7.8.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/api-derive": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/api": "10.9.1", + "@polkadot/api-augment": "10.9.1", + "@polkadot/api-base": "10.9.1", + "@polkadot/rpc-core": "10.9.1", + "@polkadot/types": "10.9.1", + "@polkadot/types-codec": "10.9.1", + "@polkadot/util": "^12.3.1", + "@polkadot/util-crypto": "^12.3.1", + "rxjs": "^7.8.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/keyring": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "12.3.2", + "@polkadot/util-crypto": "12.3.2", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "12.3.2", + "@polkadot/util-crypto": "12.3.2" + } + }, + "node_modules/@polkadot/networks": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "12.3.2", + "@substrate/ss58-registry": "^1.40.0", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/rpc-augment": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/rpc-core": "10.9.1", + "@polkadot/types": "10.9.1", + "@polkadot/types-codec": "10.9.1", + "@polkadot/util": "^12.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/rpc-core": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/rpc-augment": "10.9.1", + "@polkadot/rpc-provider": "10.9.1", + "@polkadot/types": "10.9.1", + "@polkadot/util": "^12.3.1", + "rxjs": "^7.8.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/rpc-provider": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/keyring": "^12.3.1", + "@polkadot/types": "10.9.1", + "@polkadot/types-support": "10.9.1", + "@polkadot/util": "^12.3.1", + "@polkadot/util-crypto": "^12.3.1", + "@polkadot/x-fetch": "^12.3.1", + "@polkadot/x-global": "^12.3.1", + "@polkadot/x-ws": "^12.3.1", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.2.1", + "nock": "^13.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@substrate/connect": "0.7.26" + } + }, + "node_modules/@polkadot/typegen": { + "version": "10.9.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@polkadot/api": "10.9.1", + "@polkadot/api-augment": "10.9.1", + "@polkadot/rpc-augment": "10.9.1", + "@polkadot/rpc-provider": "10.9.1", + "@polkadot/types": "10.9.1", + "@polkadot/types-augment": "10.9.1", + "@polkadot/types-codec": "10.9.1", + "@polkadot/types-create": "10.9.1", + "@polkadot/types-support": "10.9.1", + "@polkadot/util": "^12.3.1", + "@polkadot/util-crypto": "^12.3.1", + "@polkadot/x-ws": "^12.3.1", + "handlebars": "^4.7.7", + "tslib": "^2.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "polkadot-types-chain-info": "scripts/polkadot-types-chain-info.mjs", + "polkadot-types-from-chain": "scripts/polkadot-types-from-chain.mjs", + "polkadot-types-from-defs": "scripts/polkadot-types-from-defs.mjs", + "polkadot-types-internal-interfaces": "scripts/polkadot-types-internal-interfaces.mjs", + "polkadot-types-internal-metadata": "scripts/polkadot-types-internal-metadata.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/keyring": "^12.3.1", + "@polkadot/types-augment": "10.9.1", + "@polkadot/types-codec": "10.9.1", + "@polkadot/types-create": "10.9.1", + "@polkadot/util": "^12.3.1", + "@polkadot/util-crypto": "^12.3.1", + "rxjs": "^7.8.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-augment": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/types": "10.9.1", + "@polkadot/types-codec": "10.9.1", + "@polkadot/util": "^12.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-codec": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "^12.3.1", + "@polkadot/x-bigint": "^12.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-codec/node_modules/@polkadot/x-bigint": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-global": "12.3.2", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-create": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/types-codec": "10.9.1", + "@polkadot/util": "^12.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-known": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/networks": "^12.3.1", + "@polkadot/types": "10.9.1", + "@polkadot/types-codec": "10.9.1", + "@polkadot/types-create": "10.9.1", + "@polkadot/util": "^12.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-support": { + "version": "10.9.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/util": "^12.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/util": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-bigint": "12.3.2", + "@polkadot/x-global": "12.3.2", + "@polkadot/x-textdecoder": "12.3.2", + "@polkadot/x-textencoder": "12.3.2", + "@types/bn.js": "^5.1.1", + "bn.js": "^5.2.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/util-crypto": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@noble/curves": "1.1.0", + "@noble/hashes": "1.3.1", + "@polkadot/networks": "12.3.2", + "@polkadot/util": "12.3.2", + "@polkadot/wasm-crypto": "^7.2.1", + "@polkadot/wasm-util": "^7.2.1", + "@polkadot/x-bigint": "12.3.2", + "@polkadot/x-randomvalues": "12.3.2", + "@scure/base": "1.1.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "12.3.2" + } + }, + "node_modules/@polkadot/util-crypto/node_modules/@polkadot/wasm-bridge": { + "version": "7.2.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/wasm-util": "7.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/util-crypto/node_modules/@polkadot/wasm-crypto": { + "version": "7.2.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/wasm-bridge": "7.2.1", + "@polkadot/wasm-crypto-asmjs": "7.2.1", + "@polkadot/wasm-crypto-init": "7.2.1", + "@polkadot/wasm-crypto-wasm": "7.2.1", + "@polkadot/wasm-util": "7.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/util-crypto/node_modules/@polkadot/wasm-crypto-init": { + "version": "7.2.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/wasm-bridge": "7.2.1", + "@polkadot/wasm-crypto-asmjs": "7.2.1", + "@polkadot/wasm-crypto-wasm": "7.2.1", + "@polkadot/wasm-util": "7.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-bigint": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-global": "12.3.2", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/util/node_modules/@polkadot/x-bigint": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-global": "12.3.2", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/wasm-crypto-asmjs": { + "version": "7.2.1", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-wasm": { + "version": "7.2.1", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/wasm-util": "7.2.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-util": { + "version": "7.2.1", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/x-fetch": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-global": "12.3.2", + "node-fetch": "^3.3.1", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-global": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-randomvalues": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-global": "12.3.2", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "12.3.2", + "@polkadot/wasm-util": "*" + } + }, + "node_modules/@polkadot/x-textdecoder": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-global": "12.3.2", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-textencoder": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-global": "12.3.2", + "tslib": "^2.5.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-ws": { + "version": "12.3.2", + "license": "Apache-2.0", + "dependencies": { + "@polkadot/x-global": "12.3.2", + "tslib": "^2.5.3", + "ws": "^8.13.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.5.8", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.0", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.4", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.1.3", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.4", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@scure/base": { + "version": "1.1.1", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.25.24", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "5.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.1.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "license": "MIT", + "peer": true + }, + "node_modules/@substrate/connect": { + "version": "0.7.26", + "license": "GPL-3.0-only", + "optional": true, + "dependencies": { + "@substrate/connect-extension-protocol": "^1.0.1", + "eventemitter3": "^4.0.7", + "smoldot": "1.0.4" + } + }, + "node_modules/@substrate/connect-extension-protocol": { + "version": "1.0.1", + "license": "GPL-3.0-only", + "optional": true + }, + "node_modules/@substrate/connect/node_modules/eventemitter3": { + "version": "4.0.7", + "license": "MIT", + "optional": true + }, + "node_modules/@substrate/ss58-registry": { + "version": "1.40.0", + "license": "Apache-2.0" + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/bn.js": { + "version": "5.1.1", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.2.5", + "license": "MIT" + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/time-constants": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/validator": { + "version": "13.7.17", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/type-utils": "5.59.11", + "@typescript-eslint/utils": "5.59.11", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.59.11", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.59.9", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "5.59.9", + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/typescript-estree": "5.59.9", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.9", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/visitor-keys": "5.59.9", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/visitor-keys": "5.59.9" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/utils": "5.59.11", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.11", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.59.11", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.59.9", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.8", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "5.59.8", + "@typescript-eslint/visitor-keys": "5.59.8", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { + "version": "5.59.8", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.59.8", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.11", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.59.11", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.11", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "5.59.11", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.59.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "5.59.9", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "devOptional": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "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/ansi-escapes": { + "version": "4.3.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "license": "MIT", + "peer": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-root-path": { + "version": "3.1.0", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/arg": { + "version": "4.1.3", + "devOptional": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "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.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async": { + "version": "3.2.3", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "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/big-integer": { + "version": "1.6.51", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "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.11.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/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.8", + "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.30001502", + "electron-to-chromium": "^1.4.428", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "license": "MIT" + }, + "node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bullmq": { + "version": "3.15.8", + "license": "MIT", + "dependencies": { + "cron-parser": "^4.6.0", + "glob": "^8.0.3", + "ioredis": "^5.3.2", + "lodash": "^4.17.21", + "msgpackr": "^1.6.2", + "semver": "^7.3.7", + "tslib": "^2.0.0", + "uuid": "^9.0.0" + } + }, + "node_modules/bullmq/node_modules/brace-expansion": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/bullmq/node_modules/glob": { + "version": "8.1.0", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/bullmq/node_modules/minimatch": { + "version": "5.1.6", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/bundle-name": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-manager": { + "version": "4.1.0", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "async": "3.2.3", + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^7.10.1" + } + }, + "node_modules/cache-manager/node_modules/lru-cache": { + "version": "7.18.3", + "license": "ISC", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.12", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.1", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.2", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001502", + "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": "4.1.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "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" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.0", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.7.10", + "libphonenumber-js": "^1.10.14", + "validator": "^13.7.0" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "license": "ISC", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "2.15.3", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.5.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "devOptional": true, + "license": "MIT" + }, + "node_modules/cron": { + "version": "2.3.1", + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + } + }, + "node_modules/cron-parser": { + "version": "4.8.1", + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "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", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.3.1", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.429", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eol": { + "version": "0.9.1", + "dev": true, + "license": "MIT" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "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.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.42.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.42.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "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.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "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", + "import-fresh": "^3.0.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.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "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-airbnb-base": { + "version": "15.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "17.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0", + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.8.0", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.5.5", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "get-tsconfig": "^4.5.0", + "globby": "^13.1.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/globby": { + "version": "13.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/slash": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.14.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/eslint-plugin-n": { + "version": "15.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.11.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-nestjs": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "npm": ">=3" + } + }, + "node_modules/eslint-plugin-nestjs/node_modules/tslib": { + "version": "1.14.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/eslint-plugin-prettier": { + "version": "4.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.28.0", + "prettier": ">=2.0.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "dev": true, + "license": "ISC", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "dev": true, + "license": "Apache-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-scope": { + "version": "7.2.0", + "dev": true, + "license": "BSD-2-Clause", + "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/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.8.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/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "license": "MIT" + }, + "node_modules/execa": { + "version": "7.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "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/body-parser": { + "version": "1.20.1", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "license": "MIT" + }, + "node_modules/express/node_modules/raw-body": { + "version": "2.5.1", + "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/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.15.0", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "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", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "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.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stdin": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "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/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "dev": true, + "license": "ISC" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/handlebars": { + "version": "4.7.7", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "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/http2-wrapper": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/human-signals": { + "version": "4.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "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", + "peer": true + }, + "node_modules/ignore": { + "version": "5.2.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "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.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "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/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ioredis": { + "version": "5.3.2", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "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", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "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-negative-zero": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "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-shared-array-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "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", + "dev": true, + "license": "MIT", + "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.10", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterare": { + "version": "1.2.1", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/jest": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/execa": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/jest-changed-files/node_modules/human-signals": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/jest-changed-files/node_modules/is-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-changed-files/node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-changed-files/node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-changed-files/node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-changed-files/node_modules/strip-final-newline": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/jest-circus": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/strip-bom": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "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/joi": { + "version": "17.9.2", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.10.34", + "license": "MIT" + }, + "node_modules/license-report": { + "version": "6.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@kessler/tableify": "^1.0.2", + "debug": "^4.3.4", + "eol": "^0.9.1", + "got": "^12.6.0", + "rc": "^1.2.8", + "semver": "^7.3.8", + "tablemark": "^3.0.0", + "text-table": "^0.2.0", + "visit-values": "^2.0.0" + }, + "bin": { + "license-report": "index.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "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", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "license": "MIT" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/luxon": { + "version": "3.3.0", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "devOptional": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mock-socket": { + "version": "9.2.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/msgpackr": { + "version": "1.9.5", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.2", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.0.7" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" + } + }, + "node_modules/multer": { + "version": "1.4.4-lts.1", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "license": "MIT", + "peer": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/nock": { + "version": "13.3.1", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.21", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.0.7", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.12", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "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.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "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", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "license": "(MIT AND Zlib)", + "optional": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "peer": true, + "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/parse5": { + "version": "5.1.1", + "license": "MIT", + "peer": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "license": "MIT", + "peer": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "license": "MIT", + "peer": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "3.2.0", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.2", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/qs": { + "version": "6.11.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "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/quick-lru": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "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/rc": { + "version": "1.2.8", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "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/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redis": { + "version": "4.6.7", + "license": "MIT", + "optional": true, + "peer": true, + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.5.8", + "@redis/graph": "1.1.0", + "@redis/json": "1.0.4", + "@redis/search": "1.1.3", + "@redis/time-series": "1.0.4" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "license": "MIT", + "peer": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.11.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-alpn": { + "version": "1.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/responselike": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/mimic-fn": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-applescript/node_modules/onetime": { + "version": "5.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "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", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "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==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "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.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.5.3", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "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", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "license": "(MIT AND BSD-3-Clause)", + "peer": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/smoldot": { + "version": "1.0.4", + "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "optional": true, + "dependencies": { + "pako": "^2.0.4", + "ws": "^8.8.1" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split-text-to-chunks": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-stdin": "^5.0.1", + "minimist": "^1.2.0" + }, + "bin": { + "wordwrap": "cli.js" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "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", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/string-length": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "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.prototype.trim": { + "version": "1.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tablemark": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "sentence-case": "^3.0.4", + "split-text-to-chunks": "^1.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "license": "MIT", + "peer": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "license": "MIT", + "peer": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/time-constants": { + "version": "1.0.3", + "license": "ISC" + }, + "node_modules/titleize": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "license": "MIT" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-jest": { + "version": "29.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "7.x", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.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", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node-dev/node_modules/rimraf": { + "version": "2.7.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig/node_modules/strip-json-comments": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "2.5.3", + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "license": "MIT" + }, + "node_modules/typeorm": { + "version": "0.3.16", + "license": "MIT", + "peer": true, + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "chalk": "^4.1.2", + "cli-highlight": "^2.1.11", + "date-fns": "^2.29.3", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^8.1.0", + "mkdirp": "^2.1.3", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^2.5.0", + "uuid": "^9.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">= 12.9.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0", + "@sap/hana-client": "^2.12.25", + "better-sqlite3": "^7.1.2 || ^8.0.0", + "hdb-pool": "^0.1.6", + "ioredis": "^5.0.4", + "mongodb": "^5.2.0", + "mssql": "^9.1.1", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^5.1.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm/node_modules/brace-expansion": { + "version": "2.0.1", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typeorm/node_modules/glob": { + "version": "8.1.0", + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/minimatch": { + "version": "5.1.6", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typeorm/node_modules/mkdirp": { + "version": "2.1.6", + "license": "MIT", + "peer": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.1.3", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uid": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "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/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "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.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "devOptional": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/validator": { + "version": "13.9.0", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/visit-values": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/walker": { + "version": "1.0.8", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "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", + "dev": true, + "license": "MIT", + "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-typed-array": { + "version": "1.1.9", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "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/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.13.0", + "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/xtend": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "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", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json new file mode 100644 index 00000000..3148430e --- /dev/null +++ b/services/content-watcher/package.json @@ -0,0 +1,86 @@ +{ + "name": "content-publishing-service", + "version": "1.0.0", + "description": "A microservice to ContentPublishing graphs in DSNP/Frequency", + "main": "dist/src/main.js", + "scripts": { + "build": "npx tsc", + "start": "env TS_NODE_BASEURL=./dist/src node -r tsconfig-paths/register dist/src/main.js", + "start:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register src/main.ts", + "start:dev:docker": "npm ci && ts-node-dev -r tsconfig-paths/register src/main.ts", + "docker-build": "docker build -t content-publishing-service .", + "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", + "docker-run": "docker build -t content-publishing-service-deploy . ; docker run -p 6379:6379 --env-file .env.dev content-publishing-service-deploy", + "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs -f content-publishing-service", + "docker-stop:dev": "docker-compose -f docker-compose.dev.yaml stop", + "clean": "rm -Rf dist", + "lint": "tsc --noEmit --pretty && eslint \"**/*.ts\" --fix", + "pretest": "cp env.template .env", + "test": "jest --coverage --verbose" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/AmplicaLabs/content-publishing-service.git" + }, + "keywords": [], + "author": "", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/AmplicaLabs/content-publishing-service/issues" + }, + "homepage": "https://github.com/AmplicaLabs/content-publishing-service#readme", + "dependencies": { + "@dsnp/graph-sdk": "^0.0.11", + "@frequency-chain/api-augment": "1.7.0", + "@liaoliaots/nestjs-redis": "^9.0.5", + "@nestjs/axios": "^2.0.0", + "@nestjs/bullmq": "^10.0.0", + "@nestjs/common": "^9.4.0", + "@nestjs/config": "^2.3.1", + "@nestjs/core": "^9.4.0", + "@nestjs/event-emitter": "^1.4.1", + "@nestjs/platform-express": "^9.4.0", + "@nestjs/schedule": "^3.0.1", + "@nestjs/testing": "^9.4.0", + "@nestjs/typeorm": "^9.0.1", + "@polkadot/api": "^10.9.1", + "@polkadot/api-base": "^10.9.1", + "@polkadot/keyring": "^12.3.2", + "@polkadot/types": "^10.9.1", + "@polkadot/util": "^12.3.2", + "@polkadot/util-crypto": "^12.3.2", + "axios": "^1.3.6", + "bullmq": "^3.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.0", + "ioredis": "^5.3.2", + "joi": "^17.9.1", + "rxjs": "^7.8.1", + "time-constants": "^1.0.3" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@polkadot/typegen": "10.9.1", + "@types/jest": "^29.5.2", + "@types/time-constants": "^1.0.0", + "@typescript-eslint/parser": "^5.59.8", + "@typescript-eslint/typescript-estree": "5.59.8", + "dotenv": "^16.3.1", + "eslint": "^8.42.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.8.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-n": "^15.7.0", + "eslint-plugin-nestjs": "^1.2.3", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-promise": "^6.1.1", + "license-report": "^6.4.0", + "ts-jest": "^29.1.0", + "ts-node": "^10.9.1", + "ts-node-dev": "^2.0.0", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + } +} diff --git a/services/content-watcher/src/blockchain/blockchain-constants.ts b/services/content-watcher/src/blockchain/blockchain-constants.ts new file mode 100644 index 00000000..31ec1057 --- /dev/null +++ b/services/content-watcher/src/blockchain/blockchain-constants.ts @@ -0,0 +1,29 @@ +export namespace BlockchainConstants { + interface IExtrinsicCall { + pallet: string; + extrinsic: string; + } + + interface IChainEvent { + eventPallet: string; + event: string; + } + + interface IChainQuery { + queryPallet: string; + query: string; + } + + interface IChainRpc { + rpcPallet: string; + rpc: string; + } + + const PALLET_FREQ_TX_PYMT = 'frequencyTxPayment'; + const PALLET_STATEFUL_STORAGE = 'statefulStorage'; + + const EX_PAY_CAPACITY_BATCH = 'payWithCapacityBatchAll'; + const EX_UPSERT_PAGE = 'upsertPage'; + + const PAY_WITH_CAPACITY_BATCH: IExtrinsicCall = { pallet: PALLET_FREQ_TX_PYMT, extrinsic: EX_PAY_CAPACITY_BATCH }; +} diff --git a/services/content-watcher/src/blockchain/blockchain.module.ts b/services/content-watcher/src/blockchain/blockchain.module.ts new file mode 100644 index 00000000..facacf9c --- /dev/null +++ b/services/content-watcher/src/blockchain/blockchain.module.ts @@ -0,0 +1,15 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { Module } from '@nestjs/common'; +import { BlockchainService } from './blockchain.service'; +import { ConfigModule } from '../config/config.module'; + +@Module({ + imports: [ConfigModule], + controllers: [], + providers: [BlockchainService], + exports: [BlockchainService], +}) +export class BlockchainModule {} diff --git a/services/content-watcher/src/blockchain/blockchain.service.ts b/services/content-watcher/src/blockchain/blockchain.service.ts new file mode 100644 index 00000000..efa606a5 --- /dev/null +++ b/services/content-watcher/src/blockchain/blockchain.service.ts @@ -0,0 +1,138 @@ +/* eslint-disable no-underscore-dangle */ +import { ConfigService } from '#app/config/config.service'; +import { Injectable, Logger, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common'; +import { ApiPromise, ApiRx, HttpProvider, WsProvider } from '@polkadot/api'; +import { firstValueFrom } from 'rxjs'; +import { options } from '@frequency-chain/api-augment'; +import { KeyringPair } from '@polkadot/keyring/types'; +import { BlockHash, BlockNumber } from '@polkadot/types/interfaces'; +import { SubmittableExtrinsic } from '@polkadot/api/types'; +import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; +import { u32, Option, u128 } from '@polkadot/types'; +import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo } from '@polkadot/types/lookup'; +import { Extrinsic } from './extrinsic'; + +@Injectable() +export class BlockchainService implements OnApplicationBootstrap, OnApplicationShutdown { + public api: ApiRx; + + public apiPromise: ApiPromise; + + private configService: ConfigService; + + private logger: Logger; + + public async onApplicationBootstrap() { + const providerUrl = this.configService.frequencyUrl!; + let provider: any; + if (/^ws/.test(providerUrl.toString())) { + provider = new WsProvider(providerUrl.toString()); + } else if (/^http/.test(providerUrl.toString())) { + provider = new HttpProvider(providerUrl.toString()); + } else { + this.logger.error(`Unrecognized chain URL type: ${providerUrl.toString()}`); + throw new Error('Unrecognized chain URL type'); + } + this.api = await firstValueFrom(ApiRx.create({ provider, ...options })); + this.apiPromise = await ApiPromise.create({ provider, ...options }); + await Promise.all([firstValueFrom(this.api.isReady), this.apiPromise.isReady]); + this.logger.log('Blockchain API ready.'); + } + + public async onApplicationShutdown(signal?: string | undefined) { + const promises: Promise[] = []; + if (this.api) { + promises.push(this.api.disconnect()); + } + + if (this.apiPromise) { + promises.push(this.apiPromise.disconnect()); + } + await Promise.all(promises); + } + + constructor(configService: ConfigService) { + this.configService = configService; + this.logger = new Logger(this.constructor.name); + } + + public getBlockHash(block: BlockNumber | AnyNumber): Promise { + return this.apiPromise.rpc.chain.getBlockHash(block); + } + + public async getBlockNumberForHash(hash: string): Promise { + const block = await this.apiPromise.rpc.chain.getBlock(hash); + if (block) { + return block.block.header.number.toNumber(); + } + + this.logger.error(`No block found corresponding to hash ${hash}`); + return undefined; + } + + public createType(type: string, ...args: (any | undefined)[]) { + return this.api.registry.createType(type, ...args); + } + + public createExtrinsicCall({ pallet, extrinsic }: { pallet: string; extrinsic: string }, ...args: (any | undefined)[]): SubmittableExtrinsic<'rxjs', ISubmittableResult> { + return this.api.tx[pallet][extrinsic](...args); + } + + public createExtrinsic( + { pallet, extrinsic }: { pallet: string; extrinsic: string }, + { eventPallet, event }: { eventPallet?: string; event?: string }, + keys: KeyringPair, + ...args: (any | undefined)[] + ): Extrinsic { + const targetEvent = eventPallet && event ? this.api.events[eventPallet][event] : undefined; + return new Extrinsic(this.api, this.api.tx[pallet][extrinsic](...args), keys, targetEvent); + } + + public rpc(pallet: string, rpc: string, ...args: (any | undefined)[]): Promise { + return this.apiPromise.rpc[pallet][rpc](...args); + } + + public query(pallet: string, extrinsic: string, ...args: (any | undefined)[]): Promise { + return args ? this.apiPromise.query[pallet][extrinsic](...args) : this.apiPromise.query[pallet][extrinsic](); + } + + public async queryAt(blockHash: BlockHash, pallet: string, extrinsic: string, ...args: (any | undefined)[]): Promise { + const newApi = await this.apiPromise.at(blockHash); + return newApi.query[pallet][extrinsic](...args); + } + + public async capacityInfo(providerId: string): Promise<{ + providerId: string; + currentBlockNumber: number; + nextEpochStart: number; + remainingCapacity: bigint; + totalCapacityIssued: bigint; + currentEpoch: bigint; + }> { + const providerU64 = this.api.createType('u64', providerId); + const { epochStart }: PalletCapacityEpochInfo = await this.query('capacity', 'currentEpochInfo'); + const epochBlockLength: u32 = await this.query('capacity', 'epochLength'); + const capacityDetailsOption: Option = await this.query('capacity', 'capacityLedger', providerU64); + const { remainingCapacity, totalCapacityIssued } = capacityDetailsOption.unwrapOr({ remainingCapacity: 0, totalCapacityIssued: 0 }); + const currentBlock: u32 = await this.query('system', 'number'); + const currentEpoch = await this.getCurrentCapacityEpoch(); + return { + currentEpoch, + providerId, + currentBlockNumber: currentBlock.toNumber(), + nextEpochStart: epochStart.add(epochBlockLength).toNumber(), + remainingCapacity: typeof remainingCapacity === 'number' ? BigInt(remainingCapacity) : remainingCapacity.toBigInt(), + totalCapacityIssued: typeof totalCapacityIssued === 'number' ? BigInt(totalCapacityIssued) : totalCapacityIssued.toBigInt(), + }; + } + + public async getCurrentCapacityEpoch(): Promise { + const currentEpoch: u32 = await this.query('capacity', 'currentEpoch'); + return typeof currentEpoch === 'number' ? BigInt(currentEpoch) : currentEpoch.toBigInt(); + } + + public async getCurrentEpochLength(): Promise { + const epochLength: u32 = await this.query('capacity', 'epochLength'); + return typeof epochLength === 'number' ? epochLength : epochLength.toNumber(); + } +} diff --git a/services/content-watcher/src/blockchain/create-keys.ts b/services/content-watcher/src/blockchain/create-keys.ts new file mode 100644 index 00000000..b6fca978 --- /dev/null +++ b/services/content-watcher/src/blockchain/create-keys.ts @@ -0,0 +1,15 @@ +import { Keyring } from '@polkadot/api'; +import { KeyringPair } from '@polkadot/keyring/types'; + +// eslint-disable-next-line import/no-mutable-exports +export let keyring: Keyring; + +export function createKeys(uri: string): KeyringPair { + if (!keyring) { + keyring = new Keyring({ type: 'sr25519' }); + } + + const keys = keyring.addFromUri(uri); + + return keys; +} diff --git a/services/content-watcher/src/blockchain/event-error.ts b/services/content-watcher/src/blockchain/event-error.ts new file mode 100644 index 00000000..fdaedcd2 --- /dev/null +++ b/services/content-watcher/src/blockchain/event-error.ts @@ -0,0 +1,34 @@ +import { DispatchError } from '@polkadot/types/interfaces'; +import { SpRuntimeDispatchError } from '@polkadot/types/lookup'; + +export class EventError extends Error { + name: string = ''; + + message: string = ''; + + stack?: string = ''; + + section?: string = ''; + + rawError: DispatchError | SpRuntimeDispatchError; + + constructor(source: DispatchError | SpRuntimeDispatchError) { + super(); + + if (source.isModule) { + const decoded = source.registry.findMetaError(source.asModule); + this.name = decoded.name; + this.message = decoded.docs.join(' '); + this.section = decoded.section; + } else { + this.name = source.type; + this.message = source.type; + this.section = ''; + } + this.rawError = source; + } + + public toString() { + return `${this.section}.${this.name}: ${this.message}`; + } +} diff --git a/services/content-watcher/src/blockchain/extrinsic.ts b/services/content-watcher/src/blockchain/extrinsic.ts new file mode 100644 index 00000000..623a84e1 --- /dev/null +++ b/services/content-watcher/src/blockchain/extrinsic.ts @@ -0,0 +1,116 @@ +/** + * These helpers return a map of events, some of which contain useful data, some of which don't. + * Extrinsics that "create" records typically contain an ID of the entity they created, and this + * would be a useful value to return. However, this data seems to be nested inside an array of arrays. + * + * Ex: schemaId = events["schemas.SchemaCreated"][] + * + * To get the value associated with an event key, we would need to query inside that nested array with + * a set of arbitrary indices. Should an object at any level of that querying be undefined, the helper + * will throw an unchecked exception. + * + * To get type checking and cast a returned event as a specific event type, you can utilize TypeScripts + * type guard functionality like so: + * + * const msaCreatedEvent = events.defaultEvent; + * if (this.api.events.msa.MsaCreated.is(msaCreatedEvent)) { + * msaId = msaCreatedEvent.data.msaId; + * } + * + * Normally, I'd say the best experience is for the helper to return both the ID of the created entity + * along with a map of emitted events. But in this case, returning that value will increase the complexity + * of each helper, since each would have to check for undefined values at every lookup. So, this may be + * a rare case when it is best to simply return the map of emitted events and trust the user to look them + * up in the test. + */ + +import { ApiRx } from '@polkadot/api'; +import { SubmittableExtrinsic, ApiTypes, AugmentedEvent } from '@polkadot/api/types'; +import { Call, Event } from '@polkadot/types/interfaces'; +import { IsEvent } from '@polkadot/types/metadata/decorate/types'; +import { Codec, ISubmittableResult, AnyTuple } from '@polkadot/types/types'; +import { filter, firstValueFrom, map, pipe, tap } from 'rxjs'; +import { KeyringPair } from '@polkadot/keyring/types'; +import { ConfigService } from '#app/config/config.service'; +import { EventError } from './event-error'; + +export type EventMap = { [key: string]: Event }; + +function eventKey(event: Event): string { + return `${event.section}.${event.method}`; +} + +export type ParsedEventResult = [any, EventMap]; + +export class Extrinsic { + private event?: IsEvent; + + private extrinsic: SubmittableExtrinsic<'rxjs', T>; + + // private call: Call; + private keys: KeyringPair; + + public api: ApiRx; + + constructor(api: ApiRx, extrinsic: SubmittableExtrinsic<'rxjs', T>, keys: KeyringPair, targetEvent?: IsEvent) { + this.extrinsic = extrinsic; + this.keys = keys; + this.event = targetEvent; + this.api = api; + } + + public get targetEvent() { + return this.event; + } + + public signAndSend(nonce?: number): Promise { + return firstValueFrom( + this.extrinsic.signAndSend(this.keys, { nonce }).pipe( + filter(({ status }) => status.isInBlock || status.isFinalized), + this.parseResult(this.event), + ), + ); + } + + public getCall(): Call { + const call = this.api.createType('Call', this.extrinsic); + return call; + } + + // eslint-disable-next-line no-shadow + private parseResult(targetEvent?: AugmentedEvent) { + return pipe( + tap((result: ISubmittableResult) => { + if (result.dispatchError) { + const err = new EventError(result.dispatchError); + throw err; + } + }), + map((result: ISubmittableResult) => + result.events.reduce((acc, { event }) => { + acc[eventKey(event)] = event; + if (targetEvent && targetEvent.is(event)) { + acc.defaultEvent = event; + } + if (this.api.events.sudo.Sudid.is(event)) { + const { sudoResult } = event.data; + if (sudoResult.isErr) { + const err = new EventError(sudoResult.asErr); + throw err; + } + } + return acc; + }, {} as EventMap), + ), + map((em) => { + const result: ParsedEventResult = [undefined, {}]; + if (targetEvent && targetEvent.is(em?.defaultEvent)) { + result[0] = em.defaultEvent; + } + result[1] = em; + return result; + }), + // tap((events) => console.log(events)), + ); + } +} diff --git a/services/content-watcher/src/config/config.module.ts b/services/content-watcher/src/config/config.module.ts new file mode 100644 index 00000000..0b28661f --- /dev/null +++ b/services/content-watcher/src/config/config.module.ts @@ -0,0 +1,12 @@ +import { ConfigModule as NestConfigModule } from '@nestjs/config'; +import { Module } from '@nestjs/common'; +import { ConfigService } from './config.service'; +import { configModuleOptions } from './env.config'; + +@Module({ + imports: [NestConfigModule.forRoot(configModuleOptions)], + controllers: [], + providers: [ConfigService], + exports: [ConfigService], +}) +export class ConfigModule {} diff --git a/services/content-watcher/src/config/config.service.spec.ts b/services/content-watcher/src/config/config.service.spec.ts new file mode 100644 index 00000000..0ea8709e --- /dev/null +++ b/services/content-watcher/src/config/config.service.spec.ts @@ -0,0 +1,251 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* +https://docs.nestjs.com/fundamentals/testing#unit-testing +*/ + +import { Test } from '@nestjs/testing'; +import { describe, it, expect, beforeAll, jest } from '@jest/globals'; +import { ConfigModule } from '@nestjs/config'; +import { ConfigService } from './config.service'; +import { configModuleOptions } from './env.config'; + +const setupConfigService = async (envObj: any): Promise => { + jest.resetModules(); + Object.keys(process.env).forEach((key) => { + delete process.env[key]; + }); + process.env = { + ...envObj, + }; + const moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ + ...configModuleOptions, + ignoreEnvFile: true, + load: [() => process.env], + }), + ], + controllers: [], + providers: [ConfigService], + }).compile(); + + await ConfigModule.envVariablesLoaded; + + return moduleRef.get(ConfigService); +}; + +describe('ContentPublishingConfigService', () => { + const ALL_ENV: { [key: string]: string | undefined } = { + REDIS_URL: undefined, + FREQUENCY_URL: undefined, + PROVIDER_ID: undefined, + PROVIDER_BASE_URL: undefined, + PROVIDER_ACCESS_TOKEN: undefined, + BLOCKCHAIN_SCAN_INTERVAL_MINUTES: undefined, + QUEUE_HIGH_WATER: undefined, + PROVIDER_ACCOUNT_SEED_PHRASE: undefined, + WEBHOOK_FAILURE_THRESHOLD: undefined, + HEALTH_CHECK_SUCCESS_THRESHOLD: undefined, + WEBHOOK_RETRY_INTERVAL_SECONDS: undefined, + HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: undefined, + HEALTH_CHECK_MAX_RETRIES: undefined, + CAPACITY_LIMIT: undefined, + }; + + beforeAll(() => { + Object.keys(ALL_ENV).forEach((key) => { + ALL_ENV[key] = process.env[key]; + }); + }); + + describe('invalid environment', () => { + it('missing redis url should fail', async () => { + const { REDIS_URL: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ ...env })).rejects.toBeDefined(); + }); + + it('invalid redis url should fail', async () => { + const { REDIS_URL: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ REDIS_URL: 'invalid url', ...env })).rejects.toBeDefined(); + }); + + it('missing frequency url should fail', async () => { + const { FREQUENCY_URL: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ ...env })).rejects.toBeDefined(); + }); + + it('invalid frequency url should fail', async () => { + const { FREQUENCY_URL: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ FREQUENCY_URL: 'invalid url', ...env })).rejects.toBeDefined(); + }); + + it('missing provider id should fail', async () => { + const { PROVIDER_ID: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ ...env })).rejects.toBeDefined(); + }); + + it('invalid provider id should fail', async () => { + const { PROVIDER_ID: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ PROVIDER_ID: 'bad string', ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ PROVIDER_ID: '-1', ...env })).rejects.toBeDefined(); + }); + + it('missing provider base url should fail', async () => { + const { PROVIDER_BASE_URL: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ ...env })).rejects.toBeDefined(); + }); + + it('invalid provider base url should fail', async () => { + const { PROVIDER_BASE_URL: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ PROVIDER_BASE_URL: 'invalid url', ...env })).rejects.toBeDefined(); + }); + + it('missing provider access token should be ok', async () => { + const { PROVIDER_ACCESS_TOKEN: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ ...env })).resolves.toBeDefined(); + }); + + it('empty provider access token should fail', async () => { + const { PROVIDER_ACCESS_TOKEN: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ PROVIDER_ACCESS_TOKEN: '', ...env })).rejects.toBeDefined(); + }); + + it('invalid scan interval should fail', async () => { + const { BLOCKCHAIN_SCAN_INTERVAL_MINUTES: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_MINUTES: -1, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_MINUTES: 0, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_MINUTES: 'foo', ...env })).rejects.toBeDefined(); + }); + + it('invalid queue high water should fail', async () => { + const { QUEUE_HIGH_WATER: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ QUEUE_HIGH_WATER: -1, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ QUEUE_HIGH_WATER: 99, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ QUEUE_HIGH_WATER: 'foo', ...env })).rejects.toBeDefined(); + }); + + it('missing provider account seed phrase should fail', async () => { + const { PROVIDER_ACCOUNT_SEED_PHRASE: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ PROVIDER_ACCOUNT_SEED_PHRASE: undefined, ...env })).rejects.toBeDefined(); + }); + + it('invalid provider account seed phrase should fail', async () => { + const { PROVIDER_ACCOUNT_SEED_PHRASE: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ PROVIDER_ACCOUNT_SEED_PHRASE: 'hello, world', ...env })).rejects.toBeDefined(); + }); + + it('invalid webhook failure threshold should fail', async () => { + const { WEBHOOK_FAILURE_THRESHOLD: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ WEBHOOK_FAILURE_THRESHOLD: -1, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ WEBHOOK_FAILURE_THRESHOLD: 0, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ WEBHOOK_FAILURE_THRESHOLD: 'foo', ...env })).rejects.toBeDefined(); + }); + + it('invalid health check success threshold should fail', async () => { + const { HEALTH_CHECK_SUCCESS_THRESHOLD: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ HEALTH_CHECK_SUCCESS_THRESHOLD: -1, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ HEALTH_CHECK_SUCCESS_THRESHOLD: 0, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ HEALTH_CHECK_SUCCESS_THRESHOLD: 'foo', ...env })).rejects.toBeDefined(); + }); + + it('invalid webhook retry interval should fail', async () => { + const { WEBHOOK_RETRY_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ WEBHOOK_RETRY_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ WEBHOOK_RETRY_INTERVAL_SECONDS: 0, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ WEBHOOK_RETRY_INTERVAL_SECONDS: 'foo', ...env })).rejects.toBeDefined(); + }); + + it('invalid health check max retry interval should fail', async () => { + const { HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: 0, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: 'foo', ...env })).rejects.toBeDefined(); + }); + + it('invalid health check max retry interval should fail', async () => { + const { HEALTH_CHECK_MAX_RETRIES: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRIES: -1, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRIES: 'foo', ...env })).rejects.toBeDefined(); + }); + + it('missing capacity limits should fail', async () => { + const { CAPACITY_LIMIT: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ CAPACITY_LIMIT: undefined, ...env })).rejects.toBeDefined(); + }); + + it('invalid capacity limit should fail', async () => { + const { CAPACITY_LIMIT: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ CAPACITY_LIMIT: '{ "type": "bad type", "value": 0 }', ...env })).rejects.toBeDefined(); + await expect(async () => setupConfigService({ CAPACITY_LIMIT: '{ "type": "percentage", "value": -1 }', ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ CAPACITY_LIMIT: '{ "type": "percentage", "value": 101 }', ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ CAPACITY_LIMIT: '{ "type": "amount", "value": -1 }', ...env })).rejects.toBeDefined(); + }); + }); + + describe('valid environment', () => { + let contentPublishingConfigService: ConfigService; + beforeAll(async () => { + contentPublishingConfigService = await setupConfigService(ALL_ENV); + }); + + it('should be defined', () => { + expect(contentPublishingConfigService).toBeDefined(); + }); + + it('should get redis url', () => { + expect(contentPublishingConfigService.redisUrl?.toString()).toStrictEqual(ALL_ENV.REDIS_URL?.toString()); + }); + + it('should get frequency url', () => { + expect(contentPublishingConfigService.frequencyUrl?.toString()).toStrictEqual(ALL_ENV.FREQUENCY_URL?.toString()); + }); + + it('should get provider base url', () => { + expect(contentPublishingConfigService.providerBaseUrl.toString()).toStrictEqual(ALL_ENV.PROVIDER_BASE_URL); + }); + + it('should get provider api token', () => { + expect(contentPublishingConfigService.providerApiToken!.toString()).toStrictEqual(ALL_ENV.PROVIDER_ACCESS_TOKEN); + }); + + it('should get scan interval', () => { + expect(contentPublishingConfigService.getBlockchainScanIntervalMinutes()).toStrictEqual(parseInt(ALL_ENV.BLOCKCHAIN_SCAN_INTERVAL_MINUTES as string, 10)); + }); + + it('should get queue high water mark', () => { + expect(contentPublishingConfigService.getQueueHighWater()).toStrictEqual(parseInt(ALL_ENV.QUEUE_HIGH_WATER as string, 10)); + }); + + it('should get webhook failure threshold', () => { + expect(contentPublishingConfigService.getWebhookFailureThreshold()).toStrictEqual(parseInt(ALL_ENV.WEBHOOK_FAILURE_THRESHOLD as string, 10)); + }); + + it('should get health check success threshold', () => { + expect(contentPublishingConfigService.getHealthCheckSuccessThreshold()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_SUCCESS_THRESHOLD as string, 10)); + }); + + it('should get webhook retry interval', () => { + expect(contentPublishingConfigService.getWebhookRetryIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.WEBHOOK_RETRY_INTERVAL_SECONDS as string, 10)); + }); + + it('should get health check max retry interval', () => { + expect(contentPublishingConfigService.getHealthCheckMaxRetryIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS as string, 10)); + }); + + it('should get health check max retries', () => { + expect(contentPublishingConfigService.getHealthCheckMaxRetries()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRIES as string, 10)); + }); + + it('should get provider id', () => { + expect(contentPublishingConfigService.getProviderId()).toStrictEqual(ALL_ENV.PROVIDER_ID as string); + }); + + it('should get provider seed phrase', () => { + expect(contentPublishingConfigService.getProviderAccountSeedPhrase()).toStrictEqual(ALL_ENV.PROVIDER_ACCOUNT_SEED_PHRASE); + }); + + it('should get capacity limit', () => { + expect(contentPublishingConfigService.getCapacityLimit()).toStrictEqual(JSON.parse(ALL_ENV.CAPACITY_LIMIT!)); + }); + }); +}); diff --git a/services/content-watcher/src/config/config.service.ts b/services/content-watcher/src/config/config.service.ts new file mode 100644 index 00000000..9fd6cbdb --- /dev/null +++ b/services/content-watcher/src/config/config.service.ts @@ -0,0 +1,91 @@ +/* +https://docs.nestjs.com/providers#services +*/ + +import { ICapacityLimit } from '#app/interfaces/capacity-limit.interface'; +import type { EnvironmentType } from '@dsnp/graph-sdk'; +import { Injectable } from '@nestjs/common'; +import { ConfigService as NestConfigService } from '@nestjs/config'; + +export interface ConfigEnvironmentVariables { + REDIS_URL: URL; + FREQUENCY_URL: URL; + PROVIDER_ID: string; + PROVIDER_BASE_URL: URL; + PROVIDER_ACCESS_TOKEN: string; + BLOCKCHAIN_SCAN_INTERVAL_MINUTES: number; + QUEUE_HIGH_WATER: number; + WEBHOOK_FAILURE_THRESHOLD: number; + HEALTH_CHECK_SUCCESS_THRESHOLD: number; + WEBHOOK_RETRY_INTERVAL_SECONDS: number; + HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: number; + HEALTH_CHECK_MAX_RETRIES: number; + PROVIDER_ACCOUNT_SEED_PHRASE: string; + CAPACITY_LIMIT: ICapacityLimit; +} + +/// Config service to get global app and provider-specific config values. +@Injectable() +export class ConfigService { + private capacityLimit: ICapacityLimit; + + constructor(private nestConfigService: NestConfigService) { + this.capacityLimit = JSON.parse(nestConfigService.get('CAPACITY_LIMIT')!); + } + + public get redisUrl(): URL { + return this.nestConfigService.get('REDIS_URL')!; + } + + public get frequencyUrl(): URL { + return this.nestConfigService.get('FREQUENCY_URL')!; + } + + public get providerBaseUrl(): URL { + return this.nestConfigService.get('PROVIDER_BASE_URL')!; + } + + public get providerApiToken(): string | undefined { + return this.nestConfigService.get('PROVIDER_ACCESS_TOKEN'); + } + + public getBlockchainScanIntervalMinutes(): number { + return this.nestConfigService.get('BLOCKCHAIN_SCAN_INTERVAL_MINUTES') ?? 1; + } + + public getQueueHighWater(): number { + return this.nestConfigService.get('QUEUE_HIGH_WATER')!; + } + + public getWebhookFailureThreshold(): number { + return this.nestConfigService.get('WEBHOOK_FAILURE_THRESHOLD')!; + } + + public getHealthCheckSuccessThreshold(): number { + return this.nestConfigService.get('HEALTH_CHECK_SUCCESS_THRESHOLD')!; + } + + public getWebhookRetryIntervalSeconds(): number { + return this.nestConfigService.get('WEBHOOK_RETRY_INTERVAL_SECONDS')!; + } + + public getHealthCheckMaxRetryIntervalSeconds(): number { + return this.nestConfigService.get('HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS')!; + } + + public getHealthCheckMaxRetries(): number { + return this.nestConfigService.get('HEALTH_CHECK_MAX_RETRIES')!; + } + + public getProviderId(): string { + return this.nestConfigService.get('PROVIDER_ID')!; + } + + public getProviderAccountSeedPhrase(): string { + return this.nestConfigService.get('PROVIDER_ACCOUNT_SEED_PHRASE')!; + } + + public getCapacityLimit(): ICapacityLimit { + return this.capacityLimit; + } +} diff --git a/services/content-watcher/src/config/env.config.ts b/services/content-watcher/src/config/env.config.ts new file mode 100644 index 00000000..ea6f75ec --- /dev/null +++ b/services/content-watcher/src/config/env.config.ts @@ -0,0 +1,64 @@ +import Joi from 'joi'; +import { ConfigModuleOptions } from '@nestjs/config'; +import { mnemonicValidate } from '@polkadot/util-crypto'; + +export const configModuleOptions: ConfigModuleOptions = { + isGlobal: true, + validationSchema: Joi.object({ + REDIS_URL: Joi.string().uri().required(), + FREQUENCY_URL: Joi.string().uri().required(), + PROVIDER_ID: Joi.required().custom((value: string, helpers) => { + try { + const id = BigInt(value); + if (id < 0) { + throw new Error('Provider ID must be > 0'); + } + } catch (e) { + return helpers.error('any.invalid'); + } + return value; + }), + PROVIDER_BASE_URL: Joi.string().uri().required(), + PROVIDER_ACCESS_TOKEN: Joi.string(), + BLOCKCHAIN_SCAN_INTERVAL_MINUTES: Joi.number() + .min(1) + .default(3 * 60), + QUEUE_HIGH_WATER: Joi.number().min(100).default(1000), + PROVIDER_ACCOUNT_SEED_PHRASE: Joi.string() + .required() + .custom((value: string, helpers) => { + if (!mnemonicValidate(value)) { + return helpers.error('any.invalid'); + } + return value; + }), + WEBHOOK_FAILURE_THRESHOLD: Joi.number().min(1).default(3), + WEBHOOK_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(10), + HEALTH_CHECK_SUCCESS_THRESHOLD: Joi.number().min(1).default(10), + HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(64), + HEALTH_CHECK_MAX_RETRIES: Joi.number().min(0).default(20), + CAPACITY_LIMIT: Joi.string() + .custom((value: string, helpers) => { + try { + const obj = JSON.parse(value); + const schema = Joi.object({ + type: Joi.string() + .required() + .pattern(/^(percentage|amount)$/), + value: Joi.alternatives() + .conditional('type', { is: 'percentage', then: Joi.number().min(0).max(100), otherwise: Joi.number().min(0) }) + .required(), + }); + const result = schema.validate(obj); + if (result.error) { + return helpers.error('any.invalid'); + } + } catch (e) { + return helpers.error('any.invalid'); + } + + return value; + }) + .required(), + }), +}; diff --git a/services/content-watcher/src/content-publishing-service.controller.ts b/services/content-watcher/src/content-publishing-service.controller.ts new file mode 100644 index 00000000..4053c001 --- /dev/null +++ b/services/content-watcher/src/content-publishing-service.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Get, HttpStatus, Logger } from '@nestjs/common'; + +@Controller('content-publishing-service') +export class ContentPublishingServiceController { + private readonly logger: Logger; + + constructor() { + this.logger = new Logger(this.constructor.name); + } + + // eslint-disable-next-line class-methods-use-this + @Get('health') + health() { + return { + status: HttpStatus.OK, + }; + } +} diff --git a/services/content-watcher/src/content-publishing-service.module.ts b/services/content-watcher/src/content-publishing-service.module.ts new file mode 100644 index 00000000..aa13ff72 --- /dev/null +++ b/services/content-watcher/src/content-publishing-service.module.ts @@ -0,0 +1,51 @@ +import { Module } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { BullModule } from '@nestjs/bullmq'; +import { ScheduleModule } from '@nestjs/schedule'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { ContentPublishingServiceController } from './content-publishing-service.controller'; +import { ConfigService } from './config/config.service'; +import { ConfigModule } from './config/config.module'; +import { DevelopmentController } from './development.controller'; +import { BlockchainModule } from './blockchain/blockchain.module'; + +@Module({ + imports: [ + BullModule, + ConfigModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + EventEmitterModule.forRoot({ + // Use this instance throughout the application + global: true, + // set this to `true` to use wildcards + wildcard: false, + // the delimiter used to segment namespaces + delimiter: '.', + // set this to `true` if you want to emit the newListener event + newListener: false, + // set this to `true` if you want to emit the removeListener event + removeListener: false, + // the maximum amount of listeners that can be assigned to an event + maxListeners: 10, + // show event name in memory leak message when more than maximum amount of listeners is assigned + verboseMemoryLeak: false, + // disable throwing uncaughtException if an error event is emitted and it has no listeners + ignoreErrors: false, + }), + ScheduleModule.forRoot(), + BlockchainModule, + ], + providers: [ConfigService], + controllers: process.env?.ENABLE_DEV_CONTROLLER === 'true' ? [DevelopmentController, ContentPublishingServiceController] : [ContentPublishingServiceController], + exports: [], +}) +export class ContentPublishingServiceModule {} diff --git a/services/content-watcher/src/development.controller.ts b/services/content-watcher/src/development.controller.ts new file mode 100644 index 00000000..1c5812f8 --- /dev/null +++ b/services/content-watcher/src/development.controller.ts @@ -0,0 +1,21 @@ +/* +This is a controller providing some endpoints useful for development and testing. +To use it, simply rename and remove the '.dev' extension +*/ + +// eslint-disable-next-line max-classes-per-file +import { Controller, Logger, Post, Body, Param, Query, HttpException, HttpStatus } from '@nestjs/common'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Queue } from 'bullmq'; +import { plainToClass } from 'class-transformer'; +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import Redis from 'ioredis'; + +@Controller('content-publishing-service/dev') +export class DevelopmentController { + private readonly logger: Logger; + + constructor() { + this.logger = new Logger(this.constructor.name); + } +} diff --git a/services/content-watcher/src/interfaces/capacity-limit.interface.ts b/services/content-watcher/src/interfaces/capacity-limit.interface.ts new file mode 100644 index 00000000..d2b1d858 --- /dev/null +++ b/services/content-watcher/src/interfaces/capacity-limit.interface.ts @@ -0,0 +1,4 @@ +export interface ICapacityLimit { + type: 'percentage' | 'amount'; + value: number; +} diff --git a/services/content-watcher/src/main.ts b/services/content-watcher/src/main.ts new file mode 100644 index 00000000..05fafc2d --- /dev/null +++ b/services/content-watcher/src/main.ts @@ -0,0 +1,38 @@ +import { NestFactory } from '@nestjs/core'; +import { Logger, ValidationPipe } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ContentPublishingServiceModule } from './content-publishing-service.module'; + +const logger = new Logger('main'); + +// Monkey-patch BigInt so that JSON.stringify will work +// eslint-disable-next-line +BigInt.prototype['toJSON'] = function () { return this.toString() }; + +async function bootstrap() { + const app = await NestFactory.create(ContentPublishingServiceModule, { + logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error', 'warn', 'log'], + }); + + // Get event emitter & register a shutdown listener + const eventEmitter = app.get(EventEmitter2); + eventEmitter.on('shutdown', async () => { + logger.warn('Received shutdown event'); + await app.close(); + }); + + try { + app.enableShutdownHooks(); + app.useGlobalPipes(new ValidationPipe()); + await app.listen(3000); + } catch (e) { + await app.close(); + logger.log('****** MAIN CATCH ********'); + logger.error(e); + if (e instanceof Error) { + logger.error(e.stack); + } + } +} + +bootstrap(); diff --git a/services/content-watcher/tsconfig.json b/services/content-watcher/tsconfig.json new file mode 100644 index 00000000..5a2efd47 --- /dev/null +++ b/services/content-watcher/tsconfig.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Base", + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "baseUrl": "./src", + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "module": "CommonJS", + "moduleResolution": "node", + "noImplicitAny": false, + "noImplicitThis": false, + "outDir": "dist", + "paths": { + "#app/*": [ + "*" + ] + }, + "resolveJsonModule": true, + "sourceMap": true, + "strict": true, + "skipLibCheck": true, + "strictPropertyInitialization": false, + "target": "es2022", + "typeRoots": [ + "node_modules/@types" + ] + }, + "include": [ + "./**/*.ts" + ], + "exclude": [ + "node_modules/**", + "./dist/**", + "/tools/**" + ] +} From 007ac628434dc93e35f6a53c3e58b2ea3733d95b Mon Sep 17 00:00:00 2001 From: Aramik Date: Wed, 23 Aug 2023 10:09:13 -0700 Subject: [PATCH 003/137] init monorepo for nestjs (#8) --- services/content-watcher/.gitignore | 1 + services/content-watcher/INSTALLING.md | 6 -- .../api/src/api.controller.ts} | 4 +- .../api/src/api.module.ts} | 6 +- .../src/blockchain/blockchain-constants.ts | 0 .../api}/src/blockchain/blockchain.module.ts | 0 .../api}/src/blockchain/blockchain.service.ts | 2 +- .../api}/src/blockchain/create-keys.ts | 0 .../api}/src/blockchain/event-error.ts | 0 .../api}/src/blockchain/extrinsic.ts | 2 +- .../api}/src/config/config.module.ts | 0 .../api}/src/config/config.service.spec.ts | 30 -------- .../api}/src/config/config.service.ts | 13 +--- .../{ => apps/api}/src/config/env.config.ts | 2 - .../api}/src/development.controller.ts | 2 +- .../interfaces/capacity-limit.interface.ts | 0 .../{ => apps/api}/src/main.ts | 4 +- .../apps/api/tsconfig.app.json | 9 +++ .../apps/worker/src/consumer.ts | 18 +++++ .../apps/worker/src/event.listener.ts | 19 +++++ .../content-watcher/apps/worker/src/main.ts | 10 +++ .../apps/worker/src/worker.module.ts | 22 ++++++ .../apps/worker/src/worker.service.ts | 9 +++ .../apps/worker/tsconfig.app.json | 9 +++ services/content-watcher/env.template | 4 - .../content-watcher/libs/common/src/index.ts | 8 ++ .../libs/common/tsconfig.lib.json | 9 +++ services/content-watcher/nest-cli.json | 37 +++++++++ services/content-watcher/package-lock.json | 18 ++--- services/content-watcher/package.json | 13 ++-- services/content-watcher/tsconfig.json | 76 ++++++++++--------- 31 files changed, 214 insertions(+), 119 deletions(-) rename services/content-watcher/{src/content-publishing-service.controller.ts => apps/api/src/api.controller.ts} (77%) rename services/content-watcher/{src/content-publishing-service.module.ts => apps/api/src/api.module.ts} (88%) rename services/content-watcher/{ => apps/api}/src/blockchain/blockchain-constants.ts (100%) rename services/content-watcher/{ => apps/api}/src/blockchain/blockchain.module.ts (100%) rename services/content-watcher/{ => apps/api}/src/blockchain/blockchain.service.ts (98%) rename services/content-watcher/{ => apps/api}/src/blockchain/create-keys.ts (100%) rename services/content-watcher/{ => apps/api}/src/blockchain/event-error.ts (100%) rename services/content-watcher/{ => apps/api}/src/blockchain/extrinsic.ts (98%) rename services/content-watcher/{ => apps/api}/src/config/config.module.ts (100%) rename services/content-watcher/{ => apps/api}/src/config/config.service.spec.ts (88%) rename services/content-watcher/{ => apps/api}/src/config/config.service.ts (84%) rename services/content-watcher/{ => apps/api}/src/config/env.config.ts (95%) rename services/content-watcher/{ => apps/api}/src/development.controller.ts (93%) rename services/content-watcher/{ => apps/api}/src/interfaces/capacity-limit.interface.ts (100%) rename services/content-watcher/{ => apps/api}/src/main.ts (86%) create mode 100644 services/content-watcher/apps/api/tsconfig.app.json create mode 100644 services/content-watcher/apps/worker/src/consumer.ts create mode 100644 services/content-watcher/apps/worker/src/event.listener.ts create mode 100644 services/content-watcher/apps/worker/src/main.ts create mode 100644 services/content-watcher/apps/worker/src/worker.module.ts create mode 100644 services/content-watcher/apps/worker/src/worker.service.ts create mode 100644 services/content-watcher/apps/worker/tsconfig.app.json create mode 100644 services/content-watcher/libs/common/src/index.ts create mode 100644 services/content-watcher/libs/common/tsconfig.lib.json create mode 100644 services/content-watcher/nest-cli.json diff --git a/services/content-watcher/.gitignore b/services/content-watcher/.gitignore index b1b73f10..3ad330f8 100644 --- a/services/content-watcher/.gitignore +++ b/services/content-watcher/.gitignore @@ -3,3 +3,4 @@ dist .env* .vscode coverage +.idea \ No newline at end of file diff --git a/services/content-watcher/INSTALLING.md b/services/content-watcher/INSTALLING.md index 4112867f..4eaa5041 100644 --- a/services/content-watcher/INSTALLING.md +++ b/services/content-watcher/INSTALLING.md @@ -40,8 +40,6 @@ You must also minimally map `appendonlydir` (or the entire `dir`) to a persisten If you choose to build & deploy the application yourself, simply install the prerequisites: * NodeJS 18 -Note that, at present, due to limitaions in the `@dsnp/graph-sdk` module, the applcation can only be run on a `linux/amd64` platform. Support for other platforms is contingent on building & installing the `@dsnp/graph-sdk` module from source, which is outside the scope of this document. - To build the application: ``` npm run build @@ -62,11 +60,7 @@ The following is a list of environment variables that may be set to control the |-|-|-|-| |`FREQUENCY_URL`|**yes**|Blockchain URL|_none_| |`PROVIDER_ID`|**yes**|MSA ID of provider|_none_| -|`PROVIDER_BASE_URL`|**yes**|URL of provider graph query endpoint|_none_| -|`PROVIDER_ACCESS_TOKEN`|no|Optional access token to be used with requests to provider graph query endpoint|_none_| |`PROVIDER_ACCOUNT_SEED_PHRASE`|**yes**|Seed phrase for provider control keypair|_none_| |`REDIS_URL`|**yes**|URL used to connect to Redis instance|_none_
\*preset to the internal Redis URL in the standalone container| |`BLOCKCHAIN_SCAN_INTERVAL_MINUTES`|no|# of minutes to wait in between scans of the blockchain|180| |`QUEUE_HIGH_WATER`|no|# of pending graph scan queue entries to allow before pausing blockchain scanning until the next scan cycle|1000| -|`GRAPH_ENVIRONMENT_TYPE`|**yes**|Indicates which blockchain network to connect to.
Possible values:
* `Mainnet`
* `Rococo`
* `Dev`|_none_| -|`GRAPH_ENVIRONMENT_DEV_CONFIG`|no
*required for 'Dev' Graph Environment|JSON configuration object for GraphSDK configuration. Used to test against a local development Frequency node|_none_| diff --git a/services/content-watcher/src/content-publishing-service.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts similarity index 77% rename from services/content-watcher/src/content-publishing-service.controller.ts rename to services/content-watcher/apps/api/src/api.controller.ts index 4053c001..8221f0a1 100644 --- a/services/content-watcher/src/content-publishing-service.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, HttpStatus, Logger } from '@nestjs/common'; -@Controller('content-publishing-service') -export class ContentPublishingServiceController { +@Controller('api') +export class ApiController { private readonly logger: Logger; constructor() { diff --git a/services/content-watcher/src/content-publishing-service.module.ts b/services/content-watcher/apps/api/src/api.module.ts similarity index 88% rename from services/content-watcher/src/content-publishing-service.module.ts rename to services/content-watcher/apps/api/src/api.module.ts index aa13ff72..d7da5930 100644 --- a/services/content-watcher/src/content-publishing-service.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -3,7 +3,7 @@ import { EventEmitterModule } from '@nestjs/event-emitter'; import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { ContentPublishingServiceController } from './content-publishing-service.controller'; +import { ApiController } from './api.controller'; import { ConfigService } from './config/config.service'; import { ConfigModule } from './config/config.module'; import { DevelopmentController } from './development.controller'; @@ -45,7 +45,7 @@ import { BlockchainModule } from './blockchain/blockchain.module'; BlockchainModule, ], providers: [ConfigService], - controllers: process.env?.ENABLE_DEV_CONTROLLER === 'true' ? [DevelopmentController, ContentPublishingServiceController] : [ContentPublishingServiceController], + controllers: process.env?.ENABLE_DEV_CONTROLLER === 'true' ? [DevelopmentController, ApiController] : [ApiController], exports: [], }) -export class ContentPublishingServiceModule {} +export class ApiModule {} diff --git a/services/content-watcher/src/blockchain/blockchain-constants.ts b/services/content-watcher/apps/api/src/blockchain/blockchain-constants.ts similarity index 100% rename from services/content-watcher/src/blockchain/blockchain-constants.ts rename to services/content-watcher/apps/api/src/blockchain/blockchain-constants.ts diff --git a/services/content-watcher/src/blockchain/blockchain.module.ts b/services/content-watcher/apps/api/src/blockchain/blockchain.module.ts similarity index 100% rename from services/content-watcher/src/blockchain/blockchain.module.ts rename to services/content-watcher/apps/api/src/blockchain/blockchain.module.ts diff --git a/services/content-watcher/src/blockchain/blockchain.service.ts b/services/content-watcher/apps/api/src/blockchain/blockchain.service.ts similarity index 98% rename from services/content-watcher/src/blockchain/blockchain.service.ts rename to services/content-watcher/apps/api/src/blockchain/blockchain.service.ts index efa606a5..fae47d1c 100644 --- a/services/content-watcher/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/apps/api/src/blockchain/blockchain.service.ts @@ -1,5 +1,4 @@ /* eslint-disable no-underscore-dangle */ -import { ConfigService } from '#app/config/config.service'; import { Injectable, Logger, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common'; import { ApiPromise, ApiRx, HttpProvider, WsProvider } from '@polkadot/api'; import { firstValueFrom } from 'rxjs'; @@ -10,6 +9,7 @@ import { SubmittableExtrinsic } from '@polkadot/api/types'; import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; import { u32, Option, u128 } from '@polkadot/types'; import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo } from '@polkadot/types/lookup'; +import { ConfigService } from '../config/config.service'; import { Extrinsic } from './extrinsic'; @Injectable() diff --git a/services/content-watcher/src/blockchain/create-keys.ts b/services/content-watcher/apps/api/src/blockchain/create-keys.ts similarity index 100% rename from services/content-watcher/src/blockchain/create-keys.ts rename to services/content-watcher/apps/api/src/blockchain/create-keys.ts diff --git a/services/content-watcher/src/blockchain/event-error.ts b/services/content-watcher/apps/api/src/blockchain/event-error.ts similarity index 100% rename from services/content-watcher/src/blockchain/event-error.ts rename to services/content-watcher/apps/api/src/blockchain/event-error.ts diff --git a/services/content-watcher/src/blockchain/extrinsic.ts b/services/content-watcher/apps/api/src/blockchain/extrinsic.ts similarity index 98% rename from services/content-watcher/src/blockchain/extrinsic.ts rename to services/content-watcher/apps/api/src/blockchain/extrinsic.ts index 623a84e1..cf704912 100644 --- a/services/content-watcher/src/blockchain/extrinsic.ts +++ b/services/content-watcher/apps/api/src/blockchain/extrinsic.ts @@ -31,7 +31,7 @@ import { IsEvent } from '@polkadot/types/metadata/decorate/types'; import { Codec, ISubmittableResult, AnyTuple } from '@polkadot/types/types'; import { filter, firstValueFrom, map, pipe, tap } from 'rxjs'; import { KeyringPair } from '@polkadot/keyring/types'; -import { ConfigService } from '#app/config/config.service'; +import { ConfigService } from '../config/config.service'; import { EventError } from './event-error'; export type EventMap = { [key: string]: Event }; diff --git a/services/content-watcher/src/config/config.module.ts b/services/content-watcher/apps/api/src/config/config.module.ts similarity index 100% rename from services/content-watcher/src/config/config.module.ts rename to services/content-watcher/apps/api/src/config/config.module.ts diff --git a/services/content-watcher/src/config/config.service.spec.ts b/services/content-watcher/apps/api/src/config/config.service.spec.ts similarity index 88% rename from services/content-watcher/src/config/config.service.spec.ts rename to services/content-watcher/apps/api/src/config/config.service.spec.ts index 0ea8709e..52bdd194 100644 --- a/services/content-watcher/src/config/config.service.spec.ts +++ b/services/content-watcher/apps/api/src/config/config.service.spec.ts @@ -39,8 +39,6 @@ describe('ContentPublishingConfigService', () => { REDIS_URL: undefined, FREQUENCY_URL: undefined, PROVIDER_ID: undefined, - PROVIDER_BASE_URL: undefined, - PROVIDER_ACCESS_TOKEN: undefined, BLOCKCHAIN_SCAN_INTERVAL_MINUTES: undefined, QUEUE_HIGH_WATER: undefined, PROVIDER_ACCOUNT_SEED_PHRASE: undefined, @@ -90,26 +88,6 @@ describe('ContentPublishingConfigService', () => { await expect(setupConfigService({ PROVIDER_ID: '-1', ...env })).rejects.toBeDefined(); }); - it('missing provider base url should fail', async () => { - const { PROVIDER_BASE_URL: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ ...env })).rejects.toBeDefined(); - }); - - it('invalid provider base url should fail', async () => { - const { PROVIDER_BASE_URL: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ PROVIDER_BASE_URL: 'invalid url', ...env })).rejects.toBeDefined(); - }); - - it('missing provider access token should be ok', async () => { - const { PROVIDER_ACCESS_TOKEN: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ ...env })).resolves.toBeDefined(); - }); - - it('empty provider access token should fail', async () => { - const { PROVIDER_ACCESS_TOKEN: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ PROVIDER_ACCESS_TOKEN: '', ...env })).rejects.toBeDefined(); - }); - it('invalid scan interval should fail', async () => { const { BLOCKCHAIN_SCAN_INTERVAL_MINUTES: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_MINUTES: -1, ...env })).rejects.toBeDefined(); @@ -200,14 +178,6 @@ describe('ContentPublishingConfigService', () => { expect(contentPublishingConfigService.frequencyUrl?.toString()).toStrictEqual(ALL_ENV.FREQUENCY_URL?.toString()); }); - it('should get provider base url', () => { - expect(contentPublishingConfigService.providerBaseUrl.toString()).toStrictEqual(ALL_ENV.PROVIDER_BASE_URL); - }); - - it('should get provider api token', () => { - expect(contentPublishingConfigService.providerApiToken!.toString()).toStrictEqual(ALL_ENV.PROVIDER_ACCESS_TOKEN); - }); - it('should get scan interval', () => { expect(contentPublishingConfigService.getBlockchainScanIntervalMinutes()).toStrictEqual(parseInt(ALL_ENV.BLOCKCHAIN_SCAN_INTERVAL_MINUTES as string, 10)); }); diff --git a/services/content-watcher/src/config/config.service.ts b/services/content-watcher/apps/api/src/config/config.service.ts similarity index 84% rename from services/content-watcher/src/config/config.service.ts rename to services/content-watcher/apps/api/src/config/config.service.ts index 9fd6cbdb..ff789c50 100644 --- a/services/content-watcher/src/config/config.service.ts +++ b/services/content-watcher/apps/api/src/config/config.service.ts @@ -2,17 +2,14 @@ https://docs.nestjs.com/providers#services */ -import { ICapacityLimit } from '#app/interfaces/capacity-limit.interface'; -import type { EnvironmentType } from '@dsnp/graph-sdk'; import { Injectable } from '@nestjs/common'; import { ConfigService as NestConfigService } from '@nestjs/config'; +import { ICapacityLimit } from '../interfaces/capacity-limit.interface'; export interface ConfigEnvironmentVariables { REDIS_URL: URL; FREQUENCY_URL: URL; PROVIDER_ID: string; - PROVIDER_BASE_URL: URL; - PROVIDER_ACCESS_TOKEN: string; BLOCKCHAIN_SCAN_INTERVAL_MINUTES: number; QUEUE_HIGH_WATER: number; WEBHOOK_FAILURE_THRESHOLD: number; @@ -41,14 +38,6 @@ export class ConfigService { return this.nestConfigService.get('FREQUENCY_URL')!; } - public get providerBaseUrl(): URL { - return this.nestConfigService.get('PROVIDER_BASE_URL')!; - } - - public get providerApiToken(): string | undefined { - return this.nestConfigService.get('PROVIDER_ACCESS_TOKEN'); - } - public getBlockchainScanIntervalMinutes(): number { return this.nestConfigService.get('BLOCKCHAIN_SCAN_INTERVAL_MINUTES') ?? 1; } diff --git a/services/content-watcher/src/config/env.config.ts b/services/content-watcher/apps/api/src/config/env.config.ts similarity index 95% rename from services/content-watcher/src/config/env.config.ts rename to services/content-watcher/apps/api/src/config/env.config.ts index ea6f75ec..8e83ddf8 100644 --- a/services/content-watcher/src/config/env.config.ts +++ b/services/content-watcher/apps/api/src/config/env.config.ts @@ -18,8 +18,6 @@ export const configModuleOptions: ConfigModuleOptions = { } return value; }), - PROVIDER_BASE_URL: Joi.string().uri().required(), - PROVIDER_ACCESS_TOKEN: Joi.string(), BLOCKCHAIN_SCAN_INTERVAL_MINUTES: Joi.number() .min(1) .default(3 * 60), diff --git a/services/content-watcher/src/development.controller.ts b/services/content-watcher/apps/api/src/development.controller.ts similarity index 93% rename from services/content-watcher/src/development.controller.ts rename to services/content-watcher/apps/api/src/development.controller.ts index 1c5812f8..5aed5f47 100644 --- a/services/content-watcher/src/development.controller.ts +++ b/services/content-watcher/apps/api/src/development.controller.ts @@ -11,7 +11,7 @@ import { plainToClass } from 'class-transformer'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; import Redis from 'ioredis'; -@Controller('content-publishing-service/dev') +@Controller('api/dev') export class DevelopmentController { private readonly logger: Logger; diff --git a/services/content-watcher/src/interfaces/capacity-limit.interface.ts b/services/content-watcher/apps/api/src/interfaces/capacity-limit.interface.ts similarity index 100% rename from services/content-watcher/src/interfaces/capacity-limit.interface.ts rename to services/content-watcher/apps/api/src/interfaces/capacity-limit.interface.ts diff --git a/services/content-watcher/src/main.ts b/services/content-watcher/apps/api/src/main.ts similarity index 86% rename from services/content-watcher/src/main.ts rename to services/content-watcher/apps/api/src/main.ts index 05fafc2d..cf1ce08c 100644 --- a/services/content-watcher/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -1,7 +1,7 @@ import { NestFactory } from '@nestjs/core'; import { Logger, ValidationPipe } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { ContentPublishingServiceModule } from './content-publishing-service.module'; +import { ApiModule } from './api.module'; const logger = new Logger('main'); @@ -10,7 +10,7 @@ const logger = new Logger('main'); BigInt.prototype['toJSON'] = function () { return this.toString() }; async function bootstrap() { - const app = await NestFactory.create(ContentPublishingServiceModule, { + const app = await NestFactory.create(ApiModule, { logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error', 'warn', 'log'], }); diff --git a/services/content-watcher/apps/api/tsconfig.app.json b/services/content-watcher/apps/api/tsconfig.app.json new file mode 100644 index 00000000..e2e0b2ff --- /dev/null +++ b/services/content-watcher/apps/api/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": false, + "outDir": "../../dist/apps/api" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/services/content-watcher/apps/worker/src/consumer.ts b/services/content-watcher/apps/worker/src/consumer.ts new file mode 100644 index 00000000..23fb3fee --- /dev/null +++ b/services/content-watcher/apps/worker/src/consumer.ts @@ -0,0 +1,18 @@ +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Job } from 'bullmq'; + +@Processor('exampleQueue', { + concurrency: 2, +}) +export class ExampleConsumer extends WorkerHost { + // eslint-disable-next-line class-methods-use-this + async process(job: Job): Promise { + console.log(job.data); + } + + // eslint-disable-next-line class-methods-use-this + @OnWorkerEvent('completed') + onCompleted() { + // do some stuff + } +} diff --git a/services/content-watcher/apps/worker/src/event.listener.ts b/services/content-watcher/apps/worker/src/event.listener.ts new file mode 100644 index 00000000..8f9cd3cb --- /dev/null +++ b/services/content-watcher/apps/worker/src/event.listener.ts @@ -0,0 +1,19 @@ +import { QueueEventsListener, QueueEventsHost, OnQueueEvent } from '@nestjs/bullmq'; + +@QueueEventsListener('exampleQueue') +export class ExampleQueueEvents extends QueueEventsHost { + startTime: number; + + constructor() { + super(); + this.startTime = new Date().getTime(); + } + + @OnQueueEvent('drained') + onDrained({ jobId }: { jobId: string }) { + // do some stuff + + const elapsed = new Date().getTime(); + console.log((elapsed - this.startTime) / 1000); + } +} diff --git a/services/content-watcher/apps/worker/src/main.ts b/services/content-watcher/apps/worker/src/main.ts new file mode 100644 index 00000000..5663f684 --- /dev/null +++ b/services/content-watcher/apps/worker/src/main.ts @@ -0,0 +1,10 @@ +import { NestFactory } from '@nestjs/core'; +import { WorkerModule } from './worker.module'; +import { WorkerService } from './worker.service'; + +async function bootstrap() { + const app = await NestFactory.createApplicationContext(WorkerModule); + const appService = app.get(WorkerService); + console.log(appService.getHello()); +} +bootstrap(); diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts new file mode 100644 index 00000000..1a33a57e --- /dev/null +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bullmq'; +import { WorkerService } from './worker.service'; +import { ExampleConsumer } from './consumer'; +import { ExampleQueueEvents } from './event.listener'; + +@Module({ + imports: [ + BullModule.forRoot({ + connection: { + host: 'localhost', + port: 6379, + enableOfflineQueue: false, + }, + }), + BullModule.registerQueue({ + name: 'testQueue', + }), + ], + providers: [WorkerService, ExampleConsumer, ExampleQueueEvents], +}) +export class WorkerModule {} diff --git a/services/content-watcher/apps/worker/src/worker.service.ts b/services/content-watcher/apps/worker/src/worker.service.ts new file mode 100644 index 00000000..bcc16565 --- /dev/null +++ b/services/content-watcher/apps/worker/src/worker.service.ts @@ -0,0 +1,9 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class WorkerService { + // eslint-disable-next-line class-methods-use-this + getHello(): string { + return 'Hello World from Worker!'; + } +} diff --git a/services/content-watcher/apps/worker/tsconfig.app.json b/services/content-watcher/apps/worker/tsconfig.app.json new file mode 100644 index 00000000..fc3f5ed8 --- /dev/null +++ b/services/content-watcher/apps/worker/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": false, + "outDir": "../../dist/apps/worker" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index 410c1410..8afa0581 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -1,9 +1,7 @@ # Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development FREQUENCY_URL=ws://0.0.0.0:9944 PROVIDER_ID=1 -PROVIDER_BASE_URL=https://some-provider/api/v1.0.0 REDIS_URL=redis://0.0.0.0:6379 -PROVIDER_ACCESS_TOKEN=some-token BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 PROVIDER_ACCOUNT_SEED_PHRASE='come finish flower cinnamon blame year glad tank domain hunt release fatigue' @@ -14,5 +12,3 @@ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRIES=4 CAPACITY_LIMIT='{"type":"percentage", "value":80}' -# An optional bearer token may be specified for provider authentication -PROVIDER_ACCESS_TOKEN=some-token diff --git a/services/content-watcher/libs/common/src/index.ts b/services/content-watcher/libs/common/src/index.ts new file mode 100644 index 00000000..95037a7e --- /dev/null +++ b/services/content-watcher/libs/common/src/index.ts @@ -0,0 +1,8 @@ +export interface LocationDto { + name: string; + accuracy?: number; + altitude?: number; + longitude?: number; + radius?: number; + units?: 'cm' | 'm' | 'km' | 'inches' | 'feet' | 'miles'; +} diff --git a/services/content-watcher/libs/common/tsconfig.lib.json b/services/content-watcher/libs/common/tsconfig.lib.json new file mode 100644 index 00000000..8fdbf52b --- /dev/null +++ b/services/content-watcher/libs/common/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/common" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/services/content-watcher/nest-cli.json b/services/content-watcher/nest-cli.json new file mode 100644 index 00000000..dca51c9f --- /dev/null +++ b/services/content-watcher/nest-cli.json @@ -0,0 +1,37 @@ +{ + "projects": { + "common": { + "type": "library", + "root": "libs/common", + "entryFile": "index", + "sourceRoot": "libs/common/src", + "compilerOptions": { + "tsConfigPath": "libs/common/tsconfig.lib.json" + } + }, + "api": { + "type": "application", + "root": "apps/api", + "entryFile": "main", + "sourceRoot": "apps/api/src", + "compilerOptions": { + "tsConfigPath": "apps/api/tsconfig.app.json" + } + }, + "worker": { + "type": "application", + "root": "apps/worker", + "entryFile": "main", + "sourceRoot": "apps/worker/src", + "compilerOptions": { + "tsConfigPath": "apps/worker/tsconfig.app.json" + } + } + }, + "compilerOptions": { + "tsConfigPath": "apps/api/tsconfig.app.json" + }, + "monorepo": true, + "root": "apps/api", + "sourceRoot": "apps/api/src" +} \ No newline at end of file diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 1f9f486e..214304b0 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -1,15 +1,14 @@ { "name": "content-publishing-service", - "version": "1.0.0", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "content-publishing-service", - "version": "1.0.0", + "version": "0.1.0", "license": "Apache-2.0", "dependencies": { - "@dsnp/graph-sdk": "^0.0.11", "@frequency-chain/api-augment": "1.7.0", "@liaoliaots/nestjs-redis": "^9.0.5", "@nestjs/axios": "^2.0.0", @@ -679,14 +678,6 @@ "node": ">=12" } }, - "node_modules/@dsnp/graph-sdk": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@dsnp/graph-sdk/-/graph-sdk-0.0.11.tgz", - "integrity": "sha512-GQJ+gnXLku1HyyVJfHM4CgMtv0GAi00jhNVnPlmhe5bMwkfj6Pg1ZCS8/fKgRiu8G5PAVGGCSInA/2iyBZC33g==", - "engines": { - "node": "^14.0.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, @@ -4177,9 +4168,10 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.14.1", + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 3148430e..7887dcf1 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -1,13 +1,13 @@ { "name": "content-publishing-service", - "version": "1.0.0", - "description": "A microservice to ContentPublishing graphs in DSNP/Frequency", - "main": "dist/src/main.js", + "version": "0.1.0", + "description": "Services to publish content on DSNP/Frequency", + "main": "dist/apps/api/main.js", "scripts": { "build": "npx tsc", - "start": "env TS_NODE_BASEURL=./dist/src node -r tsconfig-paths/register dist/src/main.js", - "start:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register src/main.ts", - "start:dev:docker": "npm ci && ts-node-dev -r tsconfig-paths/register src/main.ts", + "start": "env TS_NODE_BASEURL=./dist/app/api node -r tsconfig-paths/register dist/apps/api/main.js", + "start:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", + "start:dev:docker": "npm ci && ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", "docker-build": "docker build -t content-publishing-service .", "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", "docker-run": "docker build -t content-publishing-service-deploy . ; docker run -p 6379:6379 --env-file .env.dev content-publishing-service-deploy", @@ -30,7 +30,6 @@ }, "homepage": "https://github.com/AmplicaLabs/content-publishing-service#readme", "dependencies": { - "@dsnp/graph-sdk": "^0.0.11", "@frequency-chain/api-augment": "1.7.0", "@liaoliaots/nestjs-redis": "^9.0.5", "@nestjs/axios": "^2.0.0", diff --git a/services/content-watcher/tsconfig.json b/services/content-watcher/tsconfig.json index 5a2efd47..4f5f22b5 100644 --- a/services/content-watcher/tsconfig.json +++ b/services/content-watcher/tsconfig.json @@ -1,38 +1,44 @@ { - "$schema": "https://json.schemastore.org/tsconfig", - "display": "Base", - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "baseUrl": "./src", - "esModuleInterop": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "module": "CommonJS", - "moduleResolution": "node", - "noImplicitAny": false, - "noImplicitThis": false, - "outDir": "dist", - "paths": { - "#app/*": [ - "*" - ] - }, - "resolveJsonModule": true, - "sourceMap": true, - "strict": true, - "skipLibCheck": true, - "strictPropertyInitialization": false, - "target": "es2022", - "typeRoots": [ - "node_modules/@types" - ] + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Base", + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "baseUrl": "./src", + "esModuleInterop": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "module": "CommonJS", + "moduleResolution": "node", + "noImplicitAny": false, + "noImplicitThis": false, + "outDir": "dist", + "paths": { + "#app/*": [ + "*" + ], + "@app/common": [ + "libs/common/src" + ], + "@app/common/*": [ + "libs/common/src/*" + ] }, - "include": [ - "./**/*.ts" - ], - "exclude": [ - "node_modules/**", - "./dist/**", - "/tools/**" + "resolveJsonModule": true, + "sourceMap": true, + "strict": true, + "skipLibCheck": true, + "strictPropertyInitialization": false, + "target": "es2022", + "typeRoots": [ + "node_modules/@types" ] -} + }, + "include": [ + "./**/*.ts" + ], + "exclude": [ + "node_modules/**", + "./dist/**", + "/tools/**" + ] +} \ No newline at end of file From 3245123f9575af4b1c811e060353c22c76034e08 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Wed, 23 Aug 2023 14:44:20 -0500 Subject: [PATCH 004/137] add swagger apis (#9) * add swagger placeholder * cleanup * init swagger doc app * add todo * merge main * api path --- services/content-watcher/.gitignore | 2 +- .../apps/api/src/api.controller.ts | 2 + .../apps/api/src/config/swagger_config.ts | 20 +++++++ services/content-watcher/apps/api/src/main.ts | 2 + services/content-watcher/package-lock.json | 58 ++++++++++++++++++- services/content-watcher/package.json | 1 + 6 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 services/content-watcher/apps/api/src/config/swagger_config.ts diff --git a/services/content-watcher/.gitignore b/services/content-watcher/.gitignore index 3ad330f8..a93fd541 100644 --- a/services/content-watcher/.gitignore +++ b/services/content-watcher/.gitignore @@ -3,4 +3,4 @@ dist .env* .vscode coverage -.idea \ No newline at end of file +.idea diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 8221f0a1..cf6f7ea9 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,6 +1,8 @@ import { Controller, Get, HttpStatus, Logger } from '@nestjs/common'; +import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; @Controller('api') +@ApiTags('api') export class ApiController { private readonly logger: Logger; diff --git a/services/content-watcher/apps/api/src/config/swagger_config.ts b/services/content-watcher/apps/api/src/config/swagger_config.ts new file mode 100644 index 00000000..9b059a23 --- /dev/null +++ b/services/content-watcher/apps/api/src/config/swagger_config.ts @@ -0,0 +1,20 @@ +import { INestApplication } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; + +// TODO: Add more swagger options and document the API +export const initSwagger = (app: INestApplication, apiPath: string) => { + const options = new DocumentBuilder() + .setTitle('Content Publishing Service API') + .setDescription('Content Publishing Service API') + .setVersion('1.0') + .addBearerAuth({ + type: 'http', + description: 'Enter JWT token', + }) + .addCookieAuth('SESSION') + .build(); + const document = SwaggerModule.createDocument(app, options, { + extraModels: [], + }); + SwaggerModule.setup(apiPath, app, document); +}; diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index cf1ce08c..63fe53a2 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -2,6 +2,7 @@ import { NestFactory } from '@nestjs/core'; import { Logger, ValidationPipe } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { ApiModule } from './api.module'; +import { initSwagger } from './config/swagger_config'; const logger = new Logger('main'); @@ -24,6 +25,7 @@ async function bootstrap() { try { app.enableShutdownHooks(); app.useGlobalPipes(new ValidationPipe()); + initSwagger(app, "/api/docs/swagger"); await app.listen(3000); } catch (e) { await app.close(); diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 214304b0..9f4c0a10 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -19,6 +19,7 @@ "@nestjs/event-emitter": "^1.4.1", "@nestjs/platform-express": "^9.4.0", "@nestjs/schedule": "^3.0.1", + "@nestjs/swagger": "^7.1.8", "@nestjs/testing": "^9.4.0", "@nestjs/typeorm": "^9.0.1", "@polkadot/api": "^10.9.1", @@ -1437,6 +1438,25 @@ "reflect-metadata": "^0.1.12" } }, + "node_modules/@nestjs/mapped-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz", + "integrity": "sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/platform-express": { "version": "9.4.3", "license": "MIT", @@ -1469,6 +1489,37 @@ "reflect-metadata": "^0.1.12" } }, + "node_modules/@nestjs/swagger": { + "version": "7.1.8", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.8.tgz", + "integrity": "sha512-Jpl3laGAqvyWccc3auLU0mMjl5hJ2kqzzDb63ynJi5NMbFlgBwrR8FCGBVstSsqL9YSJWLR4L1BZzVmVExcY+g==", + "dependencies": { + "@nestjs/mapped-types": "2.0.2", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "3.2.0", + "swagger-ui-dist": "5.3.1" + }, + "peerDependencies": { + "@fastify/static": "^6.0.0", + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, "node_modules/@nestjs/testing": { "version": "9.4.3", "license": "MIT", @@ -3002,7 +3053,6 @@ }, "node_modules/argparse": { "version": "2.0.1", - "dev": true, "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { @@ -6844,7 +6894,6 @@ }, "node_modules/js-yaml": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8714,6 +8763,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-ui-dist": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.3.1.tgz", + "integrity": "sha512-El78OvXp9zMasfPrshtkW1CRx8AugAKoZuGGOTW+8llJzOV1RtDJYqQRz/6+2OakjeWWnZuRlN2Qj1Y0ilux3w==" + }, "node_modules/synckit": { "version": "0.8.5", "dev": true, diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 7887dcf1..bd5fdb7e 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -40,6 +40,7 @@ "@nestjs/event-emitter": "^1.4.1", "@nestjs/platform-express": "^9.4.0", "@nestjs/schedule": "^3.0.1", + "@nestjs/swagger": "^7.1.8", "@nestjs/testing": "^9.4.0", "@nestjs/typeorm": "^9.0.1", "@polkadot/api": "^10.9.1", From c3d23bca2eda74ae93e978a419507c10bcd4d53d Mon Sep 17 00:00:00 2001 From: Aramik Date: Fri, 25 Aug 2023 13:39:39 -0700 Subject: [PATCH 005/137] define api endpoints and contract (#15) * define api endpoints and contract * minor description for swagger * some PR feedback * update swagger --- .../apps/api/src/api.controller.ts | 78 +- .../apps/api/src/config/swagger_config.ts | 5 +- .../apps/api/src/generate-metadata.ts | 10 + services/content-watcher/apps/api/src/main.ts | 2 +- .../content-watcher/apps/api/src/metadata.ts | 9 + .../libs/common/src/dtos/activity.dto.ts | 98 + .../libs/common/src/dtos/announcement.dto.ts | 43 + .../libs/common/src/dtos/common.dto.ts | 18 + .../content-watcher/libs/common/src/index.ts | 11 +- services/content-watcher/package-lock.json | 1863 ++++++++++++++++- services/content-watcher/package.json | 4 + 11 files changed, 2080 insertions(+), 61 deletions(-) create mode 100644 services/content-watcher/apps/api/src/generate-metadata.ts create mode 100644 services/content-watcher/apps/api/src/metadata.ts create mode 100644 services/content-watcher/libs/common/src/dtos/activity.dto.ts create mode 100644 services/content-watcher/libs/common/src/dtos/announcement.dto.ts create mode 100644 services/content-watcher/libs/common/src/dtos/common.dto.ts diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index cf6f7ea9..369d2916 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,8 +1,10 @@ -import { Controller, Get, HttpStatus, Logger } from '@nestjs/common'; -import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Logger, Param, Post, Put, UploadedFile, UploadedFiles, UseInterceptors } from '@nestjs/common'; +import { v4 as uuidv4 } from 'uuid'; +import { FilesInterceptor } from '@nestjs/platform-express'; +import { ApiBody, ApiConsumes } from '@nestjs/swagger'; +import { BroadcastDto, ReactionDto, ReplyDto, UpdateDto, ProfileDto, AnnouncementResponseDto, FilesUploadDto, UploadResponseDto } from '../../../libs/common/src'; @Controller('api') -@ApiTags('api') export class ApiController { private readonly logger: Logger; @@ -17,4 +19,74 @@ export class ApiController { status: HttpStatus.OK, }; } + + @Put('asset/upload') + @UseInterceptors(FilesInterceptor('files')) + @HttpCode(202) + @ApiConsumes('multipart/form-data') + @ApiBody({ + description: 'Asset files', + type: FilesUploadDto, + }) + // eslint-disable-next-line no-undef + async assetUpload(@UploadedFiles() files: Array): Promise { + this.logger.log(`upload ${files.length}`); + return { + assetIds: files.map((_) => uuidv4()), + }; + } + + @Post('content/:userDsnpId/broadcast') + @HttpCode(202) + async broadcast(@Param('userDsnpId') userDsnpId: string, @Body() broadcastDto: BroadcastDto): Promise { + this.logger.log(`broadcast ${userDsnpId}`); + return { + referenceId: uuidv4(), + }; + } + + @Post('content/:userDsnpId/reply') + @HttpCode(202) + async reply(@Param('userDsnpId') userDsnpId: string, @Body() replyDto: ReplyDto): Promise { + this.logger.log(`reply ${userDsnpId}`); + return { + referenceId: uuidv4(), + }; + } + + @Post('content/:userDsnpId/reaction') + @HttpCode(202) + async reaction(@Param('userDsnpId') userDsnpId: string, @Body() reactionDto: ReactionDto): Promise { + this.logger.log(`reaction ${userDsnpId}`); + return { + referenceId: uuidv4(), + }; + } + + @Put('content/:userDsnpId/:targetContentHash') + @HttpCode(202) + async update(@Param('userDsnpId') userDsnpId: string, @Param('targetContentHash') targetContentHash: string, @Body() updateDto: UpdateDto): Promise { + this.logger.log(`update ${userDsnpId}/${targetContentHash}`); + return { + referenceId: uuidv4(), + }; + } + + @Delete('content/:userDsnpId/:targetContentHash') + @HttpCode(202) + async delete(@Param('userDsnpId') userDsnpId: string, @Param('targetContentHash') targetContentHash: string): Promise { + this.logger.log(`delete ${userDsnpId}/${targetContentHash}`); + return { + referenceId: uuidv4(), + }; + } + + @Put('profile/:userDsnpId') + @HttpCode(202) + async profile(@Param('userDsnpId') userDsnpId: string, @Body() profileDto: ProfileDto): Promise { + this.logger.log(`profile ${userDsnpId}`); + return { + referenceId: uuidv4(), + }; + } } diff --git a/services/content-watcher/apps/api/src/config/swagger_config.ts b/services/content-watcher/apps/api/src/config/swagger_config.ts index 9b059a23..2c6b98e1 100644 --- a/services/content-watcher/apps/api/src/config/swagger_config.ts +++ b/services/content-watcher/apps/api/src/config/swagger_config.ts @@ -1,8 +1,8 @@ import { INestApplication } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import metadata from '../metadata'; -// TODO: Add more swagger options and document the API -export const initSwagger = (app: INestApplication, apiPath: string) => { +export const initSwagger = async (app: INestApplication, apiPath: string) => { const options = new DocumentBuilder() .setTitle('Content Publishing Service API') .setDescription('Content Publishing Service API') @@ -13,6 +13,7 @@ export const initSwagger = (app: INestApplication, apiPath: string) => { }) .addCookieAuth('SESSION') .build(); + await SwaggerModule.loadPluginMetadata(metadata); const document = SwaggerModule.createDocument(app, options, { extraModels: [], }); diff --git a/services/content-watcher/apps/api/src/generate-metadata.ts b/services/content-watcher/apps/api/src/generate-metadata.ts new file mode 100644 index 00000000..10f263f1 --- /dev/null +++ b/services/content-watcher/apps/api/src/generate-metadata.ts @@ -0,0 +1,10 @@ +import { PluginMetadataGenerator } from '@nestjs/cli/lib/compiler/plugins'; +import { ReadonlyVisitor } from '@nestjs/swagger/dist/plugin'; + +const generator = new PluginMetadataGenerator(); +generator.generate({ + visitors: [new ReadonlyVisitor({ introspectComments: true, pathToSource: __dirname })], + outputDir: __dirname, + watch: false, + tsconfigPath: 'apps/api/tsconfig.app.json', +}); diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index 63fe53a2..8cba5acd 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -25,7 +25,7 @@ async function bootstrap() { try { app.enableShutdownHooks(); app.useGlobalPipes(new ValidationPipe()); - initSwagger(app, "/api/docs/swagger"); + await initSwagger(app, '/api/docs/swagger'); await app.listen(3000); } catch (e) { await app.close(); diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts new file mode 100644 index 00000000..91a614a2 --- /dev/null +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -0,0 +1,9 @@ +/* eslint-disable */ +export default async () => { + const t = { + ["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"), + ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), + ["../../../libs/common/src/dtos/common.dto"]: await import("../../../libs/common/src/dtos/common.dto") + }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/activity.dto"), { "LinkDto": { href: { required: true, type: () => String }, name: { required: false, type: () => String } }, "LocationDto": { name: { required: true, type: () => String }, accuracy: { required: false, type: () => Number }, altitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String }, height: { required: false, type: () => Number }, width: { required: false, type: () => Number }, duration: { required: false, type: () => String } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String }, mentionedId: { required: false, type: () => String } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: true, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String }, href: { required: false, type: () => String } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String }, published: { required: true, type: () => String }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "UpdateDto": { targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].AnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String }, apply: { required: true, type: () => Number }, inReplyTo: { required: true, type: () => String } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/common.dto"), { "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }]] } }; +}; \ No newline at end of file diff --git a/services/content-watcher/libs/common/src/dtos/activity.dto.ts b/services/content-watcher/libs/common/src/dtos/activity.dto.ts new file mode 100644 index 00000000..1b37f27a --- /dev/null +++ b/services/content-watcher/libs/common/src/dtos/activity.dto.ts @@ -0,0 +1,98 @@ +/** + * File name should always end with `.dto.ts` for swagger metadata generator to get picked up + */ +// eslint-disable-next-line no-shadow,max-classes-per-file +export enum UnitTypeDto { + CM = 'cm', + M = 'm', + KM = 'km', + INCHES = 'inches', + FEET = 'feet', + MILES = 'miles', +} + +// eslint-disable-next-line no-shadow +export enum TagTypeDto { + Mention = 'mention', + Hashtag = 'hashtag', +} + +// eslint-disable-next-line no-shadow +export enum AttachmentTypeDto { + LINK = 'link', + IMAGE = 'image', + AUDIO = 'audio', + VIDEO = 'video', +} + +export class LinkDto { + href: string; + + name?: string; +} + +export class LocationDto { + name: string; + + accuracy?: number; + + altitude?: number; + + longitude?: number; + + radius?: number; + + units?: UnitTypeDto; +} + +export class AssetReferenceDto { + referenceId: string; + + height?: number; + + width?: number; + + duration?: string; +} + +export class TagDto { + type: TagTypeDto; + + name?: string; + + mentionedId?: string; +} + +export class AssetDto { + type: AttachmentTypeDto; + + references: Array; + + name?: string; + + href?: string; +} + +export class BaseActivityDto { + name?: string; + + tag?: Array; + + location?: LocationDto; +} + +export class NoteActivityDto extends BaseActivityDto { + content: string; + + published: string; + + assets?: Array; +} + +export class ProfileActivityDto extends BaseActivityDto { + icon?: Array; + + summary?: string; + + published?: string; +} diff --git a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts new file mode 100644 index 00000000..f684c4e1 --- /dev/null +++ b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts @@ -0,0 +1,43 @@ +/** + * File name should always end with `.dto.ts` for swagger metadata generator to get picked up + */ +// eslint-disable-next-line max-classes-per-file +import { NoteActivityDto, ProfileActivityDto } from './activity.dto'; + +// eslint-disable-next-line no-shadow +export enum AnnouncementTypeDto { + TOMBSTONE = 'tombstone', + BROADCAST = 'broadcast', + REPLY = 'reply', + REACTION = 'reaction', + PROFILE = 'profile', + UPDATE = 'update', +} + +export class BroadcastDto { + content: NoteActivityDto; +} + +export class ReplyDto { + inReplyTo: string; + + content: NoteActivityDto; +} + +export class UpdateDto { + targetAnnouncementType: AnnouncementTypeDto; + + content: NoteActivityDto; +} + +export class ReactionDto { + emoji: string; + + apply: number; + + inReplyTo: string; +} + +export class ProfileDto { + profile: ProfileActivityDto; +} diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts new file mode 100644 index 00000000..9fd836aa --- /dev/null +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -0,0 +1,18 @@ +/** + * File name should always end with `.dto.ts` for swagger metadata generator to get picked up + */ +// eslint-disable-next-line max-classes-per-file +import { ApiProperty } from '@nestjs/swagger'; + +export class AnnouncementResponseDto { + referenceId: string; +} + +export class UploadResponseDto { + assetIds: Array; +} + +export class FilesUploadDto { + @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } }) + files: any[]; +} diff --git a/services/content-watcher/libs/common/src/index.ts b/services/content-watcher/libs/common/src/index.ts index 95037a7e..9e674f38 100644 --- a/services/content-watcher/libs/common/src/index.ts +++ b/services/content-watcher/libs/common/src/index.ts @@ -1,8 +1,3 @@ -export interface LocationDto { - name: string; - accuracy?: number; - altitude?: number; - longitude?: number; - radius?: number; - units?: 'cm' | 'm' | 'km' | 'inches' | 'feet' | 'miles'; -} +export * from './dtos/announcement.dto'; +export * from './dtos/activity.dto'; +export * from './dtos/common.dto'; diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 9f4c0a10..f26ec156 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -28,6 +28,8 @@ "@polkadot/types": "^10.9.1", "@polkadot/util": "^12.3.2", "@polkadot/util-crypto": "^12.3.2", + "@types/multer": "^1.4.7", + "@types/uuid": "^9.0.2", "axios": "^1.3.6", "bullmq": "^3.0.0", "class-transformer": "^0.5.1", @@ -39,6 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", + "@nestjs/cli": "^10.1.14", "@polkadot/typegen": "10.9.1", "@types/jest": "^29.5.2", "@types/time-constants": "^1.0.0", @@ -83,6 +86,129 @@ "node": ">=6.0.0" } }, + "node_modules/@angular-devkit/core": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.0.tgz", + "integrity": "sha512-l1k6Rqm3YM16BEn3CWyQKrk9xfu+2ux7Bw3oS+h1TO4/RoxO2PgHj8LLRh/WNrYVarhaqO7QZ5ePBkXNMkzJ1g==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@angular-devkit/core/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 + }, + "node_modules/@angular-devkit/core/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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.0.tgz", + "integrity": "sha512-QMDJXPE0+YQJ9Ap3MMzb0v7rx6ZbBEokmHgpdIjN3eILYmbAdsSGE8HTV8NjS9nKmcyE9OGzFCMb7PFrDTlTAw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.0", + "jsonc-parser": "3.2.0", + "magic-string": "0.30.1", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.2.0.tgz", + "integrity": "sha512-f3HjrDvSrRMvESogLsqsZXsEg//trIBySCHRXCglPrWLVdBbIRctGOhXqZoclRxXimIKUx14zLsOWzDwZG8+HQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.0", + "@angular-devkit/schematics": "16.2.0", + "ansi-colors": "4.1.3", + "inquirer": "8.2.4", + "symbol-observable": "4.0.0", + "yargs-parser": "21.1.1" + }, + "bin": { + "schematics": "bin/schematics.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.22.5", "dev": true, @@ -668,6 +794,16 @@ "license": "MIT", "peer": true }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "devOptional": true, @@ -1255,6 +1391,16 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "devOptional": true, @@ -1334,6 +1480,114 @@ "bullmq": "^3.0.0" } }, + "node_modules/@nestjs/cli": { + "version": "10.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.1.14.tgz", + "integrity": "sha512-oxfoebzrq6g+MKc6FRx2O8D86Vk0ViEmlP4B1E3dzwC3X5yjxlA1IDulLrVz3VIpGjuyuXmrQjjd8l0NUVZiKg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.0", + "@angular-devkit/schematics": "16.2.0", + "@angular-devkit/schematics-cli": "16.2.0", + "@nestjs/schematics": "^10.0.1", + "chalk": "4.1.2", + "chokidar": "3.5.3", + "cli-table3": "0.6.3", + "commander": "4.1.1", + "fork-ts-checker-webpack-plugin": "8.0.0", + "inquirer": "8.2.6", + "node-emoji": "1.11.0", + "ora": "5.4.1", + "os-name": "4.0.1", + "rimraf": "4.4.1", + "shelljs": "0.8.5", + "source-map-support": "0.5.21", + "tree-kill": "1.2.2", + "tsconfig-paths": "4.2.0", + "tsconfig-paths-webpack-plugin": "4.1.0", + "typescript": "5.1.6", + "webpack": "5.88.2", + "webpack-node-externals": "3.0.0" + }, + "bin": { + "nest": "bin/nest.js" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "@swc/cli": "^0.1.62", + "@swc/core": "^1.3.62" + }, + "peerDependenciesMeta": { + "@swc/cli": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/rimraf": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", + "dev": true, + "dependencies": { + "glob": "^9.2.0" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@nestjs/common": { "version": "9.4.3", "license": "MIT", @@ -1489,6 +1743,109 @@ "reflect-metadata": "^0.1.12" } }, + "node_modules/@nestjs/schematics": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.2.tgz", + "integrity": "sha512-DaZZjymYoIfRqC5W62lnYXIIods1PDY6CGc8+IpRwyinzffjKxZ3DF3exu+mdyvllzkXo9DTXkoX4zOPSJHCkw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.1.8", + "@angular-devkit/schematics": "16.1.8", + "comment-json": "4.2.3", + "jsonc-parser": "3.2.0", + "pluralize": "8.0.0" + }, + "peerDependencies": { + "typescript": ">=4.8.2" + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { + "version": "16.1.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.8.tgz", + "integrity": "sha512-dSRD/+bGanArIXkj+kaU1kDFleZeQMzmBiOXX+pK0Ah9/0Yn1VmY3RZh1zcX9vgIQXV+t7UPrTpOjaERMUtVGw==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { + "version": "16.1.8", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.1.8.tgz", + "integrity": "sha512-6LyzMdFJs337RTxxkI2U1Ndw0CW5mMX/aXWl8d7cW2odiSrAg8IdlMqpc+AM8+CPfsB0FtS1aWkEZqJLT0jHOg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.1.8", + "jsonc-parser": "3.2.0", + "magic-string": "0.30.0", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nestjs/schematics/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 + }, + "node_modules/@nestjs/schematics/node_modules/magic-string": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", + "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@nestjs/schematics/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, + "engines": { + "node": ">= 8" + } + }, "node_modules/@nestjs/swagger": { "version": "7.1.8", "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.8.tgz", @@ -2396,6 +2753,71 @@ "@types/node": "*" } }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.44.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", + "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "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": "4.17.36", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", + "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.6", "dev": true, @@ -2409,6 +2831,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "dev": true, @@ -2442,29 +2869,76 @@ "node_modules/@types/json-schema": { "version": "7.0.12", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/json5": { "version": "0.0.29", "dev": true, "license": "MIT" }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/multer": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", + "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "20.2.5", "license": "MIT" }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "node_modules/@types/prettier": { "version": "2.7.3", "dev": true, "license": "MIT" }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, "node_modules/@types/semver": { "version": "7.5.0", "dev": true, "license": "MIT", "peer": true }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "dev": true, @@ -2485,6 +2959,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/uuid": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", + "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==" + }, "node_modules/@types/validator": { "version": "13.7.17", "license": "MIT" @@ -2917,39 +3396,206 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/accepts": { - "version": "1.3.8", - "license": "MIT", + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, - "node_modules/acorn": { - "version": "8.8.2", - "devOptional": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "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 + }, + "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 + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "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, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" } }, - "node_modules/acorn-jsx": { - "version": "5.3.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 + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "devOptional": true, + "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, + "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, + "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 + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "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/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@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.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "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 + }, + "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 + }, + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.8.2", + "devOptional": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -2970,11 +3616,67 @@ "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, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.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 + }, + "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, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -2989,7 +3691,6 @@ "version": "0.21.3", "dev": true, "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -3089,6 +3790,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true + }, "node_modules/array-union": { "version": "2.1.0", "dev": true, @@ -3270,8 +3977,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/big-integer": { "version": "1.6.51", @@ -3289,6 +3995,55 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/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, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bn.js": { "version": "5.2.1", "license": "MIT" @@ -3634,6 +4389,12 @@ "node": ">=10" } }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "node_modules/chokidar": { "version": "3.5.3", "dev": true, @@ -3671,6 +4432,15 @@ "node": ">= 6" } }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/ci-info": { "version": "3.8.0", "dev": true, @@ -3704,6 +4474,18 @@ "validator": "^13.7.0" } }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cli-highlight": { "version": "2.1.11", "license": "ISC", @@ -3759,6 +4541,42 @@ "node": ">=10" } }, + "node_modules/cli-spinners": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", + "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/cliui": { "version": "8.0.1", "license": "ISC", @@ -3771,6 +4589,15 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cluster-key-slot": { "version": "1.1.2", "license": "Apache-2.0", @@ -3818,6 +4645,31 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-json": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", + "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", + "dev": true, + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "dev": true, @@ -3893,6 +4745,22 @@ "node": ">= 0.10" } }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "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/create-require": { "version": "1.1.1", "devOptional": true, @@ -4013,7 +4881,6 @@ "version": "4.3.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4050,6 +4917,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "dev": true, @@ -4217,6 +5096,15 @@ "node": ">= 0.8" } }, + "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==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -4239,7 +5127,6 @@ "version": "1.3.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -4291,6 +5178,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-module-lexer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", + "dev": true + }, "node_modules/es-set-tostringtag": { "version": "2.0.1", "dev": true, @@ -4744,7 +5637,6 @@ "version": "5.1.1", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -4882,7 +5774,6 @@ "version": "4.3.0", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -4910,6 +5801,15 @@ "version": "5.0.1", "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, + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "7.1.1", "dev": true, @@ -5045,6 +5945,20 @@ "node": ">= 0.8" } }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "dev": true, @@ -5132,6 +6046,30 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/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, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "dev": true, @@ -5239,6 +6177,34 @@ "is-callable": "^1.1.3" } }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", + "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^7.0.1", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" + } + }, "node_modules/form-data": { "version": "4.0.0", "license": "MIT", @@ -5283,6 +6249,26 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", + "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", + "dev": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "license": "ISC" @@ -5436,6 +6422,12 @@ "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 + }, "node_modules/globals": { "version": "13.20.0", "dev": true, @@ -5579,6 +6571,15 @@ "node": ">=8" } }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.0", "dev": true, @@ -5703,8 +6704,7 @@ "url": "https://feross.org/support" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.2.4", @@ -5773,6 +6773,46 @@ "dev": true, "license": "ISC" }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/internal-slot": { "version": "1.0.5", "dev": true, @@ -5786,6 +6826,15 @@ "node": ">= 0.4" } }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ioredis": { "version": "5.3.2", "license": "MIT", @@ -5831,8 +6880,7 @@ "node_modules/is-arrayish": { "version": "0.2.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-bigint": { "version": "1.0.4", @@ -5973,6 +7021,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "dev": true, @@ -6097,6 +7154,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-weakref": { "version": "1.0.2", "dev": true, @@ -6921,8 +7990,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -6949,6 +8017,24 @@ "node": ">=6" } }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.2", "dev": true, @@ -7016,8 +8102,16 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "dev": true, - "license": "MIT", - "peer": true + "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, + "engines": { + "node": ">=6.11.5" + } }, "node_modules/locate-path": { "version": "6.0.0", @@ -7061,6 +8155,22 @@ "dev": true, "license": "MIT" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lower-case": { "version": "2.0.2", "dev": true, @@ -7097,6 +8207,30 @@ "node": ">=12" } }, + "node_modules/macos-release": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", + "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "3.1.0", "dev": true, @@ -7141,6 +8275,18 @@ "node": ">= 0.6" } }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "license": "MIT" @@ -7244,6 +8390,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "license": "MIT", @@ -7308,6 +8463,12 @@ "node": ">= 6.0.0" } }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "node_modules/mz": { "version": "2.7.0", "license": "MIT", @@ -7363,6 +8524,12 @@ "node": ">= 10.13" } }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, "node_modules/node-domexception": { "version": "1.0.0", "funding": [ @@ -7380,6 +8547,15 @@ "node": ">=10.5.0" } }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, "node_modules/node-fetch": { "version": "3.3.1", "license": "MIT", @@ -7592,6 +8768,54 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-name": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", + "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", + "dev": true, + "dependencies": { + "macos-release": "^2.5.0", + "windows-release": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-cancelable": { "version": "3.0.0", "dev": true, @@ -7656,7 +8880,6 @@ "version": "5.2.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -7724,6 +8947,40 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", + "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", + "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-to-regexp": { "version": "3.2.0", "license": "MIT" @@ -7824,6 +9081,15 @@ "node": ">=8" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "dev": true, @@ -7921,6 +9187,16 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "dev": true, @@ -7988,6 +9264,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "license": "MIT", @@ -8063,6 +9348,18 @@ "node": ">=8.10.0" } }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/redis": { "version": "4.6.7", "license": "MIT", @@ -8134,6 +9431,15 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "license": "MIT", @@ -8141,6 +9447,15 @@ "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "dev": true, @@ -8222,6 +9537,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/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, + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/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, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.0.4", "dev": true, @@ -8341,6 +9693,15 @@ "node": ">=6" } }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "dev": true, @@ -8406,6 +9767,24 @@ "version": "2.1.2", "license": "MIT" }, + "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, + "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/semver": { "version": "7.5.3", "license": "ISC", @@ -8466,6 +9845,15 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/serve-static": { "version": "1.15.0", "license": "MIT", @@ -8514,6 +9902,23 @@ "node": ">=8" } }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dev": true, + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/side-channel": { "version": "1.0.4", "license": "MIT", @@ -8768,6 +10173,15 @@ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.3.1.tgz", "integrity": "sha512-El78OvXp9zMasfPrshtkW1CRx8AugAKoZuGGOTW+8llJzOV1RtDJYqQRz/6+2OakjeWWnZuRlN2Qj1Y0ilux3w==" }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, "node_modules/synckit": { "version": "0.8.5", "dev": true, @@ -8803,6 +10217,103 @@ "node": ">=6" } }, + "node_modules/terser": { + "version": "5.19.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", + "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "dev": true, + "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.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "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/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/terser-webpack-plugin/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, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/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/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 + }, "node_modules/test-exclude": { "version": "6.0.0", "dev": true, @@ -8840,6 +10351,12 @@ "node": ">=0.8" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, "node_modules/time-constants": { "version": "1.0.3", "license": "ISC" @@ -8855,6 +10372,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "dev": true, @@ -9061,6 +10590,20 @@ "node": ">=6" } }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/tsconfig/node_modules/strip-json-comments": { "version": "2.0.1", "dev": true, @@ -9307,9 +10850,10 @@ } }, "node_modules/typescript": { - "version": "5.1.3", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", "devOptional": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9354,6 +10898,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "license": "MIT", @@ -9427,7 +10980,8 @@ }, "node_modules/uuid": { "version": "9.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { "uuid": "dist/bin/uuid" } @@ -9509,6 +11063,28 @@ "makeerror": "1.0.12" } }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/web-streams-polyfill": { "version": "3.2.1", "license": "MIT", @@ -9520,6 +11096,71 @@ "version": "3.0.1", "license": "BSD-2-Clause" }, + "node_modules/webpack": { + "version": "5.88.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", + "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "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.9", + "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.7", + "watchpack": "^2.4.0", + "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-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "license": "MIT", @@ -9576,6 +11217,125 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/windows-release": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", + "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", + "dev": true, + "dependencies": { + "execa": "^4.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/windows-release/node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/windows-release/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/windows-release/node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/windows-release/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/windows-release/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, + "engines": { + "node": ">=6" + } + }, + "node_modules/windows-release/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/windows-release/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, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/windows-release/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/wordwrap": { "version": "1.0.0", "dev": true, @@ -9649,6 +11409,15 @@ "version": "4.0.0", "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==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "17.7.2", "license": "MIT", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index bd5fdb7e..5505a040 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -5,6 +5,7 @@ "main": "dist/apps/api/main.js", "scripts": { "build": "npx tsc", + "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", "start": "env TS_NODE_BASEURL=./dist/app/api node -r tsconfig-paths/register dist/apps/api/main.js", "start:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", "start:dev:docker": "npm ci && ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", @@ -36,6 +37,7 @@ "@nestjs/bullmq": "^10.0.0", "@nestjs/common": "^9.4.0", "@nestjs/config": "^2.3.1", + "@nestjs/cli": "^10.1.14", "@nestjs/core": "^9.4.0", "@nestjs/event-emitter": "^1.4.1", "@nestjs/platform-express": "^9.4.0", @@ -49,6 +51,8 @@ "@polkadot/types": "^10.9.1", "@polkadot/util": "^12.3.2", "@polkadot/util-crypto": "^12.3.2", + "@types/multer": "^1.4.7", + "@types/uuid": "^9.0.2", "axios": "^1.3.6", "bullmq": "^3.0.0", "class-transformer": "^0.5.1", From 99dc51f1723b51ab7aa9ad03b24d41be1077fde9 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Tue, 29 Aug 2023 08:33:48 -0500 Subject: [PATCH 006/137] Publisher Processor (#14) * base work * set up interface * setup interfaces * setup publisher * implement publish to publish a batch * lint * add TODOs * cleanups * refactor * update worker, refactor blockchain module * fix worker and imports * add placeholder files for tests * cleanup and address feedback * cleanups --- .../apps/api/src/api.module.ts | 2 - .../src/blockchain/blockchain-constants.ts | 0 .../src/blockchain/blockchain.module.ts | 2 +- .../src/blockchain/blockchain.service.spec.ts | 18 + .../src/blockchain/blockchain.service.ts | 6 +- .../src/blockchain/create-keys.ts | 0 .../src/blockchain/event-error.ts | 0 .../src/blockchain/extrinsic.ts | 2 +- .../src/interfaces/publisher-job.interface.ts | 10 + .../src/publisher/ipfs.publisher.spec.ts | 30 ++ .../worker/src/publisher/ipfs.publisher.ts | 86 ++++ .../worker/src/publisher/publisher.module.ts | 69 +++ .../src/publisher/publishing.service.ts | 161 +++++++ .../apps/worker/src/worker.module.ts | 51 ++- .../libs/common/src/constants.ts | 2 + services/content-watcher/package-lock.json | 395 ++---------------- services/content-watcher/package.json | 2 +- 17 files changed, 448 insertions(+), 388 deletions(-) rename services/content-watcher/apps/{api => worker}/src/blockchain/blockchain-constants.ts (100%) rename services/content-watcher/apps/{api => worker}/src/blockchain/blockchain.module.ts (80%) create mode 100644 services/content-watcher/apps/worker/src/blockchain/blockchain.service.spec.ts rename services/content-watcher/apps/{api => worker}/src/blockchain/blockchain.service.ts (96%) rename services/content-watcher/apps/{api => worker}/src/blockchain/create-keys.ts (100%) rename services/content-watcher/apps/{api => worker}/src/blockchain/event-error.ts (100%) rename services/content-watcher/apps/{api => worker}/src/blockchain/extrinsic.ts (98%) create mode 100644 services/content-watcher/apps/worker/src/interfaces/publisher-job.interface.ts create mode 100644 services/content-watcher/apps/worker/src/publisher/ipfs.publisher.spec.ts create mode 100644 services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts create mode 100644 services/content-watcher/apps/worker/src/publisher/publisher.module.ts create mode 100644 services/content-watcher/apps/worker/src/publisher/publishing.service.ts create mode 100644 services/content-watcher/libs/common/src/constants.ts diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index d7da5930..94100d93 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -7,7 +7,6 @@ import { ApiController } from './api.controller'; import { ConfigService } from './config/config.service'; import { ConfigModule } from './config/config.module'; import { DevelopmentController } from './development.controller'; -import { BlockchainModule } from './blockchain/blockchain.module'; @Module({ imports: [ @@ -42,7 +41,6 @@ import { BlockchainModule } from './blockchain/blockchain.module'; ignoreErrors: false, }), ScheduleModule.forRoot(), - BlockchainModule, ], providers: [ConfigService], controllers: process.env?.ENABLE_DEV_CONTROLLER === 'true' ? [DevelopmentController, ApiController] : [ApiController], diff --git a/services/content-watcher/apps/api/src/blockchain/blockchain-constants.ts b/services/content-watcher/apps/worker/src/blockchain/blockchain-constants.ts similarity index 100% rename from services/content-watcher/apps/api/src/blockchain/blockchain-constants.ts rename to services/content-watcher/apps/worker/src/blockchain/blockchain-constants.ts diff --git a/services/content-watcher/apps/api/src/blockchain/blockchain.module.ts b/services/content-watcher/apps/worker/src/blockchain/blockchain.module.ts similarity index 80% rename from services/content-watcher/apps/api/src/blockchain/blockchain.module.ts rename to services/content-watcher/apps/worker/src/blockchain/blockchain.module.ts index facacf9c..0205f784 100644 --- a/services/content-watcher/apps/api/src/blockchain/blockchain.module.ts +++ b/services/content-watcher/apps/worker/src/blockchain/blockchain.module.ts @@ -4,7 +4,7 @@ https://docs.nestjs.com/modules import { Module } from '@nestjs/common'; import { BlockchainService } from './blockchain.service'; -import { ConfigModule } from '../config/config.module'; +import { ConfigModule } from '../../../api/src/config/config.module'; @Module({ imports: [ConfigModule], diff --git a/services/content-watcher/apps/worker/src/blockchain/blockchain.service.spec.ts b/services/content-watcher/apps/worker/src/blockchain/blockchain.service.spec.ts new file mode 100644 index 00000000..1a172bf9 --- /dev/null +++ b/services/content-watcher/apps/worker/src/blockchain/blockchain.service.spec.ts @@ -0,0 +1,18 @@ +import { describe, it, beforeEach } from '@jest/globals'; +import { BlockchainService } from './blockchain.service'; + +describe('BlockchainService', () => { + let blockchainService: BlockchainService; + + beforeEach(async () => {}); + + describe('createExtrinsicCall', () => { + it('should return an extrinsic call', async () => { + const pallet = 'messages'; + const extrinsic = 'addIpfsMessage'; + const schemaId = 1; + const cid = 'QmRgJZmR6Z6yB5k9aLXjzJ6jG8L6tq4v4J9zQfDz7p3J9v'; + const payloadLength = 100; + }); + }); +}); diff --git a/services/content-watcher/apps/api/src/blockchain/blockchain.service.ts b/services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts similarity index 96% rename from services/content-watcher/apps/api/src/blockchain/blockchain.service.ts rename to services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts index fae47d1c..3cdd832d 100644 --- a/services/content-watcher/apps/api/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts @@ -9,7 +9,7 @@ import { SubmittableExtrinsic } from '@polkadot/api/types'; import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; import { u32, Option, u128 } from '@polkadot/types'; import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo } from '@polkadot/types/lookup'; -import { ConfigService } from '../config/config.service'; +import { ConfigService } from '../../../api/src/config/config.service'; import { Extrinsic } from './extrinsic'; @Injectable() @@ -135,4 +135,8 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS const epochLength: u32 = await this.query('capacity', 'epochLength'); return typeof epochLength === 'number' ? epochLength : epochLength.toNumber(); } + + public async capacityBatchLimit(): Promise { + return this.api.consts.frequencyTxPayment.maximumCapacityBatchLength.toNumber(); + } } diff --git a/services/content-watcher/apps/api/src/blockchain/create-keys.ts b/services/content-watcher/apps/worker/src/blockchain/create-keys.ts similarity index 100% rename from services/content-watcher/apps/api/src/blockchain/create-keys.ts rename to services/content-watcher/apps/worker/src/blockchain/create-keys.ts diff --git a/services/content-watcher/apps/api/src/blockchain/event-error.ts b/services/content-watcher/apps/worker/src/blockchain/event-error.ts similarity index 100% rename from services/content-watcher/apps/api/src/blockchain/event-error.ts rename to services/content-watcher/apps/worker/src/blockchain/event-error.ts diff --git a/services/content-watcher/apps/api/src/blockchain/extrinsic.ts b/services/content-watcher/apps/worker/src/blockchain/extrinsic.ts similarity index 98% rename from services/content-watcher/apps/api/src/blockchain/extrinsic.ts rename to services/content-watcher/apps/worker/src/blockchain/extrinsic.ts index cf704912..a435e0ab 100644 --- a/services/content-watcher/apps/api/src/blockchain/extrinsic.ts +++ b/services/content-watcher/apps/worker/src/blockchain/extrinsic.ts @@ -31,7 +31,7 @@ import { IsEvent } from '@polkadot/types/metadata/decorate/types'; import { Codec, ISubmittableResult, AnyTuple } from '@polkadot/types/types'; import { filter, firstValueFrom, map, pipe, tap } from 'rxjs'; import { KeyringPair } from '@polkadot/keyring/types'; -import { ConfigService } from '../config/config.service'; +import { ConfigService } from '../../../api/src/config/config.service'; import { EventError } from './event-error'; export type EventMap = { [key: string]: Event }; diff --git a/services/content-watcher/apps/worker/src/interfaces/publisher-job.interface.ts b/services/content-watcher/apps/worker/src/interfaces/publisher-job.interface.ts new file mode 100644 index 00000000..21a00f82 --- /dev/null +++ b/services/content-watcher/apps/worker/src/interfaces/publisher-job.interface.ts @@ -0,0 +1,10 @@ +export interface IPFSJobData { + cid: string; + payloadLength: number; +} + +export interface IPublisherJob { + id: string; + schemaId: number; + data: IPFSJobData; +} diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.spec.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.spec.ts new file mode 100644 index 00000000..7cbcf65c --- /dev/null +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.spec.ts @@ -0,0 +1,30 @@ +// test file for ipfs publisher +import { describe, it, beforeEach } from '@jest/globals'; +import { IPFSPublisher } from './ipfs.publisher'; + +describe('IPFSPublisher', () => { + let ipfsPublisher: IPFSPublisher; + + beforeEach(async () => {}); + + describe('publish', () => { + it('should return capacity used per epoch', async () => { + const messages = [ + { + schemaId: 1, + data: { + cid: 'QmRgJZmR6Z6yB5k9aLXjzJ6jG8L6tq4v4J9zQfDz7p3J9v', + payloadLength: 100, + }, + }, + { + schemaId: 1, + data: { + cid: 'QmRgJZmR6Z6yB5k9aLXjzJ6jG8L6tq4v4J9zQfDz7p3J9v', + payloadLength: 100, + }, + }, + ]; + }); + }); +}); diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts new file mode 100644 index 00000000..726cd033 --- /dev/null +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -0,0 +1,86 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { KeyringPair } from '@polkadot/keyring/types'; +import { ISubmittableResult } from '@polkadot/types/types'; +import { SubmittableExtrinsic } from '@polkadot/api-base/types'; +import { BlockchainService } from '../blockchain/blockchain.service'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { IPublisherJob } from '../interfaces/publisher-job.interface'; +import { createKeys } from '../blockchain/create-keys'; + +@Injectable() +export class IPFSPublisher { + private logger: Logger; + + constructor(private configService: ConfigService, private blockchainService: BlockchainService, private eventEmitter: EventEmitter2) { + this.logger = new Logger(IPFSPublisher.name); + } + + public async publish(messages: IPublisherJob[]): Promise<{ [key: string]: bigint }> { + const providerKeys = createKeys(this.configService.getProviderAccountSeedPhrase()); + + let batch: SubmittableExtrinsic<'rxjs', ISubmittableResult>[] = []; + const batches: SubmittableExtrinsic<'rxjs', ISubmittableResult>[][] = []; + const allowedBatchLen = await this.blockchainService.capacityBatchLimit(); + messages.forEach((message) => { + batch.push(this.blockchainService.createExtrinsicCall({ pallet: 'messages', extrinsic: 'addIpfsMessage' }, message.schemaId, message.data.cid, message.data.payloadLength)); + + if (batch.length === allowedBatchLen) { + batches.push(batch); + batch = []; + } + }); + + if (batch.length > 0) { + batches.push(batch); + } + return this.sendAndProcessChainEvents(providerKeys, batches); + } + + async sendAndProcessChainEvents(providerKeys: KeyringPair, batchesMap: SubmittableExtrinsic<'rxjs', ISubmittableResult>[][]): Promise<{ [key: string]: bigint }> { + try { + // iterate over batches and send them to the chain returning the capacity withdrawn + const batchPromises: Promise<{ [key: string]: bigint }>[] = []; + + batchesMap.forEach(async (batch) => { + batchPromises.push(this.processSingleBatch(providerKeys, batch)); + }); + + const totalCapUsedPerEpoch = await Promise.all(batchPromises); + const totalCapacityUsed = totalCapUsedPerEpoch.reduce((acc, curr) => { + const epoch = Object.keys(curr)[0]; + if (acc[epoch]) { + acc[epoch] += curr[epoch]; + } + acc[epoch] = curr[epoch]; + return acc; + }, {} as { [key: string]: bigint }); + + this.logger.debug(`Total capacity used: ${JSON.stringify(totalCapacityUsed)}`); + return totalCapacityUsed; + } catch (e) { + this.logger.error(`Error processing batches: ${e}`); + throw e; + } + } + + async processSingleBatch(providerKeys: KeyringPair, batch: SubmittableExtrinsic<'rxjs', ISubmittableResult>[]): Promise<{ [key: string]: bigint }> { + this.logger.debug(`Submitting batch of size ${batch.length}`); + try { + const currrentEpoch = await this.blockchainService.getCurrentCapacityEpoch(); + const [event, eventMap] = await this.blockchainService + .createExtrinsic({ pallet: 'frequencyTxPayment', extrinsic: 'payWithCapacityBatchAll' }, { eventPallet: 'utility', event: 'BatchCompleted' }, providerKeys, batch) + .signAndSend(); + if (!event || !this.blockchainService.api.events.utility.BatchCompleted.is(event)) { + // if we dont get any events, covering any unexpected connection errors + throw new Error(`No events were found for batch`); + } + const capacityWithDrawn = BigInt(eventMap['capacity.CapacityWithdrawn'].data[1].toString()); + this.logger.debug(`Batch processed, capacity withdrawn: ${capacityWithDrawn}`); + return { [currrentEpoch.toString()]: capacityWithDrawn }; + } catch (e) { + this.logger.error(`Error processing batch: ${e}`); + throw e; + } + } +} diff --git a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts new file mode 100644 index 00000000..de3c7fde --- /dev/null +++ b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts @@ -0,0 +1,69 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { BullModule } from '@nestjs/bullmq'; +import { Module } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { PublishingService } from './publishing.service'; +import { ConfigModule } from '../../../api/src/config/config.module'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { BlockchainModule } from '../blockchain/blockchain.module'; +import { IPFSPublisher } from './ipfs.publisher'; + +@Module({ + imports: [ + BlockchainModule, + ConfigModule, + EventEmitterModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue({ + name: 'publishQueue', + defaultJobOptions: { + attempts: 1, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }), + ], + controllers: [], + providers: [PublishingService, IPFSPublisher], + exports: [BullModule, PublishingService, IPFSPublisher], +}) +export class PublisherModule {} diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts new file mode 100644 index 00000000..88bd6f47 --- /dev/null +++ b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts @@ -0,0 +1,161 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; +import Redis from 'ioredis'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; +import { MILLISECONDS_PER_SECOND } from 'time-constants'; +import { BlockchainService } from '../blockchain/blockchain.service'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { IPublisherJob } from '../interfaces/publisher-job.interface'; +import { IPFSPublisher } from './ipfs.publisher'; +import { CAPACITY_EPOCH_TIMEOUT_NAME, SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; + +@Injectable() +@Processor('publishQueue', { + concurrency: 2, +}) +export class PublishingService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { + private logger: Logger; + + private capacityExhausted = false; + + constructor( + @InjectRedis() private cacheManager: Redis, + @InjectQueue('publishQueue') private publishQueue: Queue, + private blockchainService: BlockchainService, + private configService: ConfigService, + private ipfsPublisher: IPFSPublisher, + private schedulerRegistry: SchedulerRegistry, + private eventEmitter: EventEmitter2, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + public async onApplicationBootstrap() { + await this.checkCapacity(); + } + + public onModuleDestroy() { + try { + this.schedulerRegistry.deleteTimeout(CAPACITY_EPOCH_TIMEOUT_NAME); + } catch (e) { + // 💀 // + } + } + + async process(job: Job): Promise { + this.logger.log(`Processing job ${job.id} of type ${job.name}`); + try { + // TODO: this is only performing one message per batch, figure out how to batch multiple messages + const totalCapacityUsed = await this.ipfsPublisher.publish([job.data]); + await this.setEpochCapacity(totalCapacityUsed); + + this.logger.verbose(`Successfully completed job ${job.id}`); + return { success: true }; + } catch (e) { + this.logger.error(`Job ${job.id} failed (attempts=${job.attemptsMade})`); + if (e instanceof Error && e.message.includes('Inability to pay some fees')) { + this.eventEmitter.emit('capacity.exhausted'); + // TODO: revisit this logic + } + throw e; + } finally { + await this.checkCapacity(); + } + } + + private async setEpochCapacity(totalCapacityUsed: { [key: string]: bigint }): Promise { + Object.entries(totalCapacityUsed).forEach(async ([epoch, capacityUsed]) => { + const epochCapacityKey = `epochCapacity:${epoch}`; + + try { + const epochCapacity = BigInt((await this.cacheManager.get(epochCapacityKey)) ?? 0); + const newEpochCapacity = epochCapacity + capacityUsed; + + const epochDurationBlocks = await this.blockchainService.getCurrentEpochLength(); + const epochDuration = epochDurationBlocks * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; + + await this.cacheManager.setex(epochCapacityKey, epochDuration, newEpochCapacity.toString()); + } catch (error) { + this.logger.error(`Error setting epoch capacity: ${error}`); + + throw error; + } + }); + } + + private async checkCapacity(): Promise { + const capacityLimit = this.configService.getCapacityLimit(); + const capacity = await this.blockchainService.capacityInfo(this.configService.getProviderId()); + const { remainingCapacity } = capacity; + const { currentEpoch } = capacity; + const epochCapacityKey = `epochCapacity:${currentEpoch}`; + const epochUsedCapacity = BigInt((await this.cacheManager.get(epochCapacityKey)) ?? 0); // Fetch capacity used by the service + let outOfCapacity = remainingCapacity <= 0n; + + if (!outOfCapacity) { + this.logger.debug(`Capacity remaining: ${remainingCapacity}`); + if (capacityLimit.type === 'percentage') { + const capacityLimitPercentage = BigInt(capacityLimit.value); + const capacityLimitThreshold = (capacity.totalCapacityIssued * capacityLimitPercentage) / 100n; + this.logger.debug(`Capacity limit threshold: ${capacityLimitThreshold}`); + if (epochUsedCapacity >= capacityLimitThreshold) { + outOfCapacity = true; + this.logger.warn(`Capacity threshold reached: used ${epochUsedCapacity} of ${capacityLimitThreshold}`); + } + } else if (epochUsedCapacity >= capacityLimit.value) { + outOfCapacity = true; + this.logger.warn(`Capacity threshold reached: used ${epochUsedCapacity} of ${capacityLimit.value}`); + } + } + + if (outOfCapacity) { + await this.eventEmitter.emitAsync('capacity.exhausted'); + } else { + await this.eventEmitter.emitAsync('capacity.refilled'); + } + } + + @OnEvent('capacity.exhausted', { async: true, promisify: true }) + private async handleCapacityExhausted() { + this.logger.debug('Received capacity.exhausted event'); + this.capacityExhausted = true; + await this.publishQueue.pause(); + const capacityLimit = this.configService.getCapacityLimit(); + const capacity = await this.blockchainService.capacityInfo(this.configService.getProviderId()); + + this.logger.debug(` + Capacity limit: ${JSON.stringify(capacityLimit)} + Remaining Capacity: ${JSON.stringify(capacity.remainingCapacity.toString())})}`); + + const blocksRemaining = capacity.nextEpochStart - capacity.currentBlockNumber; + try { + this.schedulerRegistry.addTimeout( + CAPACITY_EPOCH_TIMEOUT_NAME, + setTimeout(() => this.checkCapacity(), blocksRemaining * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND), + ); + } catch (err) { + // ignore duplicate timeout + } + } + + @OnEvent('capacity.refilled', { async: true, promisify: true }) + private async handleCapacityRefilled() { + this.logger.debug('Received capacity.refilled event'); + this.capacityExhausted = false; + try { + this.schedulerRegistry.deleteTimeout(CAPACITY_EPOCH_TIMEOUT_NAME); + } catch (err) { + // ignore + } + } + + // eslint-disable-next-line class-methods-use-this + @OnWorkerEvent('completed') + onCompleted() { + // do some stuff + } +} diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts index 1a33a57e..010cd3c6 100644 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -1,22 +1,51 @@ import { Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bullmq'; +import { ScheduleModule } from '@nestjs/schedule'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { PublishingService } from './publisher/publishing.service'; +import { PublisherModule } from './publisher/publisher.module'; import { WorkerService } from './worker.service'; -import { ExampleConsumer } from './consumer'; -import { ExampleQueueEvents } from './event.listener'; +import { ConfigService } from '../../api/src/config/config.service'; +import { BlockchainModule } from './blockchain/blockchain.module'; +import { ConfigModule } from '../../api/src/config/config.module'; @Module({ imports: [ - BullModule.forRoot({ - connection: { - host: 'localhost', - port: 6379, - enableOfflineQueue: false, + BullModule, + ConfigModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], }, + true, // isGlobal + ), + EventEmitterModule.forRoot({ + // Use this instance throughout the application + global: true, + // set this to `true` to use wildcards + wildcard: false, + // the delimiter used to segment namespaces + delimiter: '.', + // set this to `true` if you want to emit the newListener event + newListener: false, + // set this to `true` if you want to emit the removeListener event + removeListener: false, + // the maximum amount of listeners that can be assigned to an event + maxListeners: 10, + // show event name in memory leak message when more than maximum amount of listeners is assigned + verboseMemoryLeak: false, + // disable throwing uncaughtException if an error event is emitted and it has no listeners + ignoreErrors: false, }), - BullModule.registerQueue({ - name: 'testQueue', - }), + ScheduleModule.forRoot(), + PublisherModule, + BlockchainModule, ], - providers: [WorkerService, ExampleConsumer, ExampleQueueEvents], + providers: [ConfigService, WorkerService, PublishingService], }) export class WorkerModule {} diff --git a/services/content-watcher/libs/common/src/constants.ts b/services/content-watcher/libs/common/src/constants.ts new file mode 100644 index 00000000..a7df8f39 --- /dev/null +++ b/services/content-watcher/libs/common/src/constants.ts @@ -0,0 +1,2 @@ +export const SECONDS_PER_BLOCK = 12; +export const CAPACITY_EPOCH_TIMEOUT_NAME = 'capacity-epoch-timeout'; diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index f26ec156..ef07ccf5 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -10,9 +10,11 @@ "license": "Apache-2.0", "dependencies": { "@frequency-chain/api-augment": "1.7.0", + "@jest/globals": "^29.5.0", "@liaoliaots/nestjs-redis": "^9.0.5", "@nestjs/axios": "^2.0.0", "@nestjs/bullmq": "^10.0.0", + "@nestjs/cli": "^10.1.14", "@nestjs/common": "^9.4.0", "@nestjs/config": "^2.3.1", "@nestjs/core": "^9.4.0", @@ -40,8 +42,6 @@ "time-constants": "^1.0.3" }, "devDependencies": { - "@jest/globals": "^29.5.0", - "@nestjs/cli": "^10.1.14", "@polkadot/typegen": "10.9.1", "@types/jest": "^29.5.2", "@types/time-constants": "^1.0.0", @@ -76,7 +76,6 @@ }, "node_modules/@ampproject/remapping": { "version": "2.2.1", - "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", @@ -90,7 +89,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.0.tgz", "integrity": "sha512-l1k6Rqm3YM16BEn3CWyQKrk9xfu+2ux7Bw3oS+h1TO4/RoxO2PgHj8LLRh/WNrYVarhaqO7QZ5ePBkXNMkzJ1g==", - "dev": true, "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", @@ -116,7 +114,6 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -131,14 +128,12 @@ "node_modules/@angular-devkit/core/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 + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/@angular-devkit/core/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, "engines": { "node": ">= 8" } @@ -147,7 +142,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.0.tgz", "integrity": "sha512-QMDJXPE0+YQJ9Ap3MMzb0v7rx6ZbBEokmHgpdIjN3eILYmbAdsSGE8HTV8NjS9nKmcyE9OGzFCMb7PFrDTlTAw==", - "dev": true, "dependencies": { "@angular-devkit/core": "16.2.0", "jsonc-parser": "3.2.0", @@ -165,7 +159,6 @@ "version": "16.2.0", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.2.0.tgz", "integrity": "sha512-f3HjrDvSrRMvESogLsqsZXsEg//trIBySCHRXCglPrWLVdBbIRctGOhXqZoclRxXimIKUx14zLsOWzDwZG8+HQ==", - "dev": true, "dependencies": { "@angular-devkit/core": "16.2.0", "@angular-devkit/schematics": "16.2.0", @@ -187,7 +180,6 @@ "version": "8.2.4", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", - "dev": true, "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -211,7 +203,6 @@ }, "node_modules/@babel/code-frame": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/highlight": "^7.22.5" @@ -222,7 +213,6 @@ }, "node_modules/@babel/compat-data": { "version": "7.22.5", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -230,7 +220,6 @@ }, "node_modules/@babel/core": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -259,12 +248,10 @@ }, "node_modules/@babel/core/node_modules/convert-source-map": { "version": "1.9.0", - "dev": true, "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -272,7 +259,6 @@ }, "node_modules/@babel/generator": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5", @@ -286,7 +272,6 @@ }, "node_modules/@babel/generator/node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -294,12 +279,10 @@ }, "node_modules/@babel/generator/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", - "dev": true, "license": "MIT" }, "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "3.1.0", @@ -308,7 +291,6 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.5", @@ -326,7 +308,6 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { "version": "5.1.1", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -334,7 +315,6 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -342,12 +322,10 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", - "dev": true, "license": "ISC" }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.5", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -355,7 +333,6 @@ }, "node_modules/@babel/helper-function-name": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.22.5", @@ -367,7 +344,6 @@ }, "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" @@ -378,7 +354,6 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" @@ -389,7 +364,6 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", @@ -407,7 +381,6 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.22.5", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -415,7 +388,6 @@ }, "node_modules/@babel/helper-simple-access": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" @@ -426,7 +398,6 @@ }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" @@ -437,7 +408,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.22.5", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -445,7 +415,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.22.5", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -453,7 +422,6 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.22.5", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -461,7 +429,6 @@ }, "node_modules/@babel/helpers": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.22.5", @@ -474,7 +441,6 @@ }, "node_modules/@babel/highlight": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.22.5", @@ -487,7 +453,6 @@ }, "node_modules/@babel/highlight/node_modules/ansi-styles": { "version": "3.2.1", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^1.9.0" @@ -498,7 +463,6 @@ }, "node_modules/@babel/highlight/node_modules/chalk": { "version": "2.4.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", @@ -511,7 +475,6 @@ }, "node_modules/@babel/highlight/node_modules/color-convert": { "version": "1.9.3", - "dev": true, "license": "MIT", "dependencies": { "color-name": "1.1.3" @@ -519,12 +482,10 @@ }, "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", - "dev": true, "license": "MIT" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.0" @@ -532,7 +493,6 @@ }, "node_modules/@babel/highlight/node_modules/has-flag": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -540,7 +500,6 @@ }, "node_modules/@babel/highlight/node_modules/supports-color": { "version": "5.5.0", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^3.0.0" @@ -551,7 +510,6 @@ }, "node_modules/@babel/parser": { "version": "7.22.5", - "dev": true, "license": "MIT", "bin": { "parser": "bin/babel-parser.js" @@ -562,7 +520,6 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -573,7 +530,6 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -584,7 +540,6 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" @@ -595,7 +550,6 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -606,7 +560,6 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -617,7 +570,6 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -631,7 +583,6 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -642,7 +593,6 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -653,7 +603,6 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -664,7 +613,6 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -675,7 +623,6 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -686,7 +633,6 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -697,7 +643,6 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -711,7 +656,6 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -736,7 +680,6 @@ }, "node_modules/@babel/template": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.5", @@ -749,7 +692,6 @@ }, "node_modules/@babel/traverse": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.5", @@ -769,7 +711,6 @@ }, "node_modules/@babel/traverse/node_modules/globals": { "version": "11.12.0", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -777,7 +718,6 @@ }, "node_modules/@babel/types": { "version": "7.22.5", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.22.5", @@ -798,7 +738,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, "optional": true, "engines": { "node": ">=0.1.90" @@ -923,7 +862,6 @@ }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", - "dev": true, "license": "ISC", "dependencies": { "camelcase": "^5.3.1", @@ -938,7 +876,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { "version": "1.0.10", - "dev": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" @@ -946,7 +883,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -958,7 +894,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.1", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^1.0.7", @@ -970,7 +905,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -981,7 +915,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { "version": "2.3.0", - "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -995,7 +928,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -1006,7 +938,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1014,7 +945,6 @@ }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1086,7 +1016,6 @@ }, "node_modules/@jest/environment": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/fake-timers": "^29.5.0", @@ -1100,7 +1029,6 @@ }, "node_modules/@jest/expect": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "expect": "^29.5.0", @@ -1112,7 +1040,6 @@ }, "node_modules/@jest/expect-utils": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "jest-get-type": "^29.4.3" @@ -1123,7 +1050,6 @@ }, "node_modules/@jest/fake-timers": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", @@ -1139,7 +1065,6 @@ }, "node_modules/@jest/globals": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/environment": "^29.5.0", @@ -1221,7 +1146,6 @@ }, "node_modules/@jest/schemas": { "version": "29.4.3", - "dev": true, "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.25.16" @@ -1301,7 +1225,6 @@ }, "node_modules/@jest/transform": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", @@ -1326,7 +1249,6 @@ }, "node_modules/@jest/transform/node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1334,12 +1256,10 @@ }, "node_modules/@jest/transform/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", - "dev": true, "license": "MIT" }, "node_modules/@jest/transform/node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "3.1.0", @@ -1348,7 +1268,6 @@ }, "node_modules/@jest/types": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.4.3", @@ -1364,7 +1283,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.0.1", @@ -1377,7 +1295,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.1", - "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1385,7 +1302,6 @@ }, "node_modules/@jridgewell/set-array": { "version": "1.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1395,7 +1311,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -1403,12 +1318,10 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", - "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", - "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -1484,7 +1397,6 @@ "version": "10.1.14", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.1.14.tgz", "integrity": "sha512-oxfoebzrq6g+MKc6FRx2O8D86Vk0ViEmlP4B1E3dzwC3X5yjxlA1IDulLrVz3VIpGjuyuXmrQjjd8l0NUVZiKg==", - "dev": true, "dependencies": { "@angular-devkit/core": "16.2.0", "@angular-devkit/schematics": "16.2.0", @@ -1532,7 +1444,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -1541,7 +1452,6 @@ "version": "9.3.5", "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", @@ -1559,7 +1469,6 @@ "version": "8.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1574,7 +1483,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", - "dev": true, "dependencies": { "glob": "^9.2.0" }, @@ -1747,7 +1655,6 @@ "version": "10.0.2", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.2.tgz", "integrity": "sha512-DaZZjymYoIfRqC5W62lnYXIIods1PDY6CGc8+IpRwyinzffjKxZ3DF3exu+mdyvllzkXo9DTXkoX4zOPSJHCkw==", - "dev": true, "dependencies": { "@angular-devkit/core": "16.1.8", "@angular-devkit/schematics": "16.1.8", @@ -1763,7 +1670,6 @@ "version": "16.1.8", "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.8.tgz", "integrity": "sha512-dSRD/+bGanArIXkj+kaU1kDFleZeQMzmBiOXX+pK0Ah9/0Yn1VmY3RZh1zcX9vgIQXV+t7UPrTpOjaERMUtVGw==", - "dev": true, "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", @@ -1789,7 +1695,6 @@ "version": "16.1.8", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.1.8.tgz", "integrity": "sha512-6LyzMdFJs337RTxxkI2U1Ndw0CW5mMX/aXWl8d7cW2odiSrAg8IdlMqpc+AM8+CPfsB0FtS1aWkEZqJLT0jHOg==", - "dev": true, "dependencies": { "@angular-devkit/core": "16.1.8", "jsonc-parser": "3.2.0", @@ -1807,7 +1712,6 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -1822,14 +1726,12 @@ "node_modules/@nestjs/schematics/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 + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/@nestjs/schematics/node_modules/magic-string": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.13" }, @@ -1841,7 +1743,6 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "dev": true, "engines": { "node": ">= 8" } @@ -2616,7 +2517,6 @@ }, "node_modules/@sinclair/typebox": { "version": "0.25.24", - "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { @@ -2632,7 +2532,6 @@ }, "node_modules/@sinonjs/commons": { "version": "3.0.0", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" @@ -2640,7 +2539,6 @@ }, "node_modules/@sinonjs/fake-timers": { "version": "10.1.0", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -2740,7 +2638,6 @@ }, "node_modules/@types/babel__traverse": { "version": "7.20.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" @@ -2774,7 +2671,6 @@ "version": "8.44.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", - "dev": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -2784,7 +2680,6 @@ "version": "3.7.4", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -2793,8 +2688,7 @@ "node_modules/@types/estree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" }, "node_modules/@types/express": { "version": "4.17.17", @@ -2820,7 +2714,6 @@ }, "node_modules/@types/graceful-fs": { "version": "4.1.6", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -2838,12 +2731,10 @@ }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", - "dev": true, "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", - "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" @@ -2851,7 +2742,6 @@ }, "node_modules/@types/istanbul-reports": { "version": "3.0.1", - "dev": true, "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" @@ -2868,7 +2758,6 @@ }, "node_modules/@types/json-schema": { "version": "7.0.12", - "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -2896,12 +2785,10 @@ "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/prettier": { "version": "2.7.3", - "dev": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -2941,7 +2828,6 @@ }, "node_modules/@types/stack-utils": { "version": "2.0.1", - "dev": true, "license": "MIT" }, "node_modules/@types/strip-bom": { @@ -2970,7 +2856,6 @@ }, "node_modules/@types/yargs": { "version": "17.0.24", - "dev": true, "license": "MIT", "dependencies": { "@types/yargs-parser": "*" @@ -2978,7 +2863,6 @@ }, "node_modules/@types/yargs-parser": { "version": "21.0.0", - "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -3400,7 +3284,6 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", - "dev": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -3409,26 +3292,22 @@ "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 + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" }, "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 + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" }, "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, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -3438,14 +3317,12 @@ "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 + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -3457,7 +3334,6 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -3466,7 +3342,6 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -3474,14 +3349,12 @@ "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 + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -3497,7 +3370,6 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", @@ -3510,7 +3382,6 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -3522,7 +3393,6 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -3536,7 +3406,6 @@ "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", - "dev": true, "dependencies": { "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" @@ -3545,14 +3414,12 @@ "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 + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" }, "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 + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/accepts": { "version": "1.3.8", @@ -3567,7 +3434,6 @@ }, "node_modules/acorn": { "version": "8.8.2", - "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3580,7 +3446,6 @@ "version": "1.9.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "dev": true, "peerDependencies": { "acorn": "^8" } @@ -3603,7 +3468,6 @@ }, "node_modules/ajv": { "version": "6.12.6", - "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -3620,7 +3484,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, "dependencies": { "ajv": "^8.0.0" }, @@ -3637,7 +3500,6 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -3652,14 +3514,12 @@ "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 + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "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, "peerDependencies": { "ajv": "^6.9.1" } @@ -3668,14 +3528,12 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, "engines": { "node": ">=6" } }, "node_modules/ansi-escapes": { "version": "4.3.2", - "dev": true, "license": "MIT", "dependencies": { "type-fest": "^0.21.3" @@ -3689,7 +3547,6 @@ }, "node_modules/ansi-escapes/node_modules/type-fest": { "version": "0.21.3", - "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" @@ -3725,7 +3582,6 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -3793,8 +3649,7 @@ "node_modules/array-timsort": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", - "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", - "dev": true + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==" }, "node_modules/array-union": { "version": "2.1.0", @@ -3891,7 +3746,6 @@ }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -3921,7 +3775,6 @@ }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", @@ -3989,7 +3842,6 @@ }, "node_modules/binary-extensions": { "version": "2.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3999,7 +3851,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -4010,7 +3861,6 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, "funding": [ { "type": "github", @@ -4034,7 +3884,6 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -4094,7 +3943,6 @@ }, "node_modules/brace-expansion": { "version": "1.1.11", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -4103,7 +3951,6 @@ }, "node_modules/braces": { "version": "3.0.2", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.0.1" @@ -4114,7 +3961,6 @@ }, "node_modules/browserslist": { "version": "4.21.8", - "dev": true, "funding": [ { "type": "opencollective", @@ -4156,7 +4002,6 @@ }, "node_modules/bser": { "version": "2.1.1", - "dev": true, "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" @@ -4333,7 +4178,6 @@ }, "node_modules/callsites": { "version": "3.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4341,7 +4185,6 @@ }, "node_modules/camelcase": { "version": "5.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4349,7 +4192,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001502", - "dev": true, "funding": [ { "type": "opencollective", @@ -4392,12 +4234,10 @@ "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "node_modules/chokidar": { "version": "3.5.3", - "dev": true, "funding": [ { "type": "individual", @@ -4423,7 +4263,6 @@ }, "node_modules/chokidar/node_modules/glob-parent": { "version": "5.1.2", - "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -4436,14 +4275,12 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, "engines": { "node": ">=6.0" } }, "node_modules/ci-info": { "version": "3.8.0", - "dev": true, "funding": [ { "type": "github", @@ -4478,7 +4315,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "dependencies": { "restore-cursor": "^3.1.0" }, @@ -4545,7 +4381,6 @@ "version": "2.9.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", - "dev": true, "engines": { "node": ">=6" }, @@ -4557,7 +4392,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", - "dev": true, "dependencies": { "string-width": "^4.2.0" }, @@ -4572,7 +4406,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true, "engines": { "node": ">= 10" } @@ -4593,7 +4426,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, "engines": { "node": ">=0.8" } @@ -4649,7 +4481,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -4658,7 +4489,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", - "dev": true, "dependencies": { "array-timsort": "^1.0.3", "core-util-is": "^1.0.3", @@ -4672,7 +4502,6 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -4716,7 +4545,6 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -4749,7 +4577,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -4785,7 +4612,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.3", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -4879,7 +4705,6 @@ }, "node_modules/deepmerge": { "version": "4.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4921,7 +4746,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, "dependencies": { "clone": "^1.0.2" }, @@ -5011,7 +4835,6 @@ }, "node_modules/diff-sequences": { "version": "29.4.3", - "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5070,7 +4893,6 @@ }, "node_modules/electron-to-chromium": { "version": "1.4.429", - "dev": true, "license": "ISC" }, "node_modules/emittery": { @@ -5100,7 +4922,6 @@ "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==", - "dev": true, "dependencies": { "once": "^1.4.0" } @@ -5109,7 +4930,6 @@ "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5125,7 +4945,6 @@ }, "node_modules/error-ex": { "version": "1.3.2", - "dev": true, "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" @@ -5181,8 +5000,7 @@ "node_modules/es-module-lexer": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", - "dev": true + "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==" }, "node_modules/es-set-tostringtag": { "version": "2.0.1", @@ -5635,7 +5453,6 @@ }, "node_modules/eslint-scope": { "version": "5.1.1", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -5722,7 +5539,6 @@ }, "node_modules/esprima": { "version": "4.0.1", - "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -5753,7 +5569,6 @@ }, "node_modules/esrecurse": { "version": "4.3.0", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -5764,7 +5579,6 @@ }, "node_modules/esrecurse/node_modules/estraverse": { "version": "5.3.0", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -5772,7 +5586,6 @@ }, "node_modules/estraverse": { "version": "4.3.0", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -5805,7 +5618,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, "engines": { "node": ">=0.8.x" } @@ -5842,7 +5654,6 @@ }, "node_modules/expect": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/expect-utils": "^29.5.0", @@ -5949,7 +5760,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -5961,7 +5771,6 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -5997,7 +5806,6 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -6019,7 +5827,6 @@ }, "node_modules/fb-watchman": { "version": "2.0.2", - "dev": true, "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" @@ -6050,7 +5857,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -6065,7 +5871,6 @@ "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, "engines": { "node": ">=0.8.0" } @@ -6083,7 +5888,6 @@ }, "node_modules/fill-range": { "version": "7.0.1", - "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -6181,7 +5985,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "chalk": "^4.1.2", @@ -6253,7 +6056,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -6266,8 +6068,7 @@ "node_modules/fs-monkey": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", - "dev": true + "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==" }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -6313,7 +6114,6 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -6341,7 +6141,6 @@ }, "node_modules/get-package-type": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8.0.0" @@ -6394,7 +6193,6 @@ }, "node_modules/glob": { "version": "7.2.3", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -6425,8 +6223,7 @@ "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 + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/globals": { "version": "13.20.0", @@ -6512,7 +6309,6 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "dev": true, "license": "ISC" }, "node_modules/grapheme-splitter": { @@ -6575,7 +6371,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", - "dev": true, "engines": { "node": ">=8" } @@ -6716,7 +6511,6 @@ }, "node_modules/import-fresh": { "version": "3.3.0", - "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -6750,7 +6544,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -6777,7 +6570,6 @@ "version": "8.2.6", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "dev": true, "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -6803,7 +6595,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6830,7 +6621,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, "engines": { "node": ">= 0.10" } @@ -6879,7 +6669,6 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "dev": true, "license": "MIT" }, "node_modules/is-bigint": { @@ -6895,7 +6684,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -6932,7 +6720,6 @@ }, "node_modules/is-core-module": { "version": "2.12.1", - "dev": true, "license": "MIT", "dependencies": { "has": "^1.0.3" @@ -6971,7 +6758,6 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6995,7 +6781,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -7025,7 +6810,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, "engines": { "node": ">=8" } @@ -7043,7 +6827,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -7158,7 +6941,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, "engines": { "node": ">=10" }, @@ -7208,12 +6990,10 @@ }, "node_modules/isexe": { "version": "2.0.0", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=8" @@ -7221,7 +7001,6 @@ }, "node_modules/istanbul-lib-instrument": { "version": "5.2.1", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", @@ -7236,7 +7015,6 @@ }, "node_modules/istanbul-lib-instrument/node_modules/semver": { "version": "6.3.1", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -7530,7 +7308,6 @@ }, "node_modules/jest-diff": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -7589,7 +7366,6 @@ }, "node_modules/jest-get-type": { "version": "29.4.3", - "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7597,7 +7373,6 @@ }, "node_modules/jest-haste-map": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", @@ -7634,7 +7409,6 @@ }, "node_modules/jest-matcher-utils": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -7648,7 +7422,6 @@ }, "node_modules/jest-message-util": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", @@ -7667,7 +7440,6 @@ }, "node_modules/jest-mock": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", @@ -7697,7 +7469,6 @@ }, "node_modules/jest-regex-util": { "version": "29.4.3", - "dev": true, "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7822,7 +7593,6 @@ }, "node_modules/jest-snapshot": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", @@ -7855,7 +7625,6 @@ }, "node_modules/jest-util": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", @@ -7919,7 +7688,6 @@ }, "node_modules/jest-worker": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -7933,7 +7701,6 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -7958,7 +7725,6 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -7973,7 +7739,6 @@ }, "node_modules/jsesc": { "version": "2.5.2", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -7989,12 +7754,10 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", - "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -8008,7 +7771,6 @@ }, "node_modules/json5": { "version": "2.2.3", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -8020,14 +7782,12 @@ "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "dependencies": { "universalify": "^2.0.0" }, @@ -8101,14 +7861,12 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "dev": true, "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, "engines": { "node": ">=6.11.5" } @@ -8159,7 +7917,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -8211,7 +7968,6 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", - "dev": true, "engines": { "node": ">=6" }, @@ -8223,7 +7979,6 @@ "version": "0.30.1", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -8262,7 +8017,6 @@ }, "node_modules/makeerror": { "version": "1.0.12", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" @@ -8279,7 +8033,6 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, "dependencies": { "fs-monkey": "^1.0.4" }, @@ -8293,7 +8046,6 @@ }, "node_modules/merge-stream": { "version": "2.0.0", - "dev": true, "license": "MIT" }, "node_modules/merge2": { @@ -8313,7 +8065,6 @@ }, "node_modules/micromatch": { "version": "4.0.5", - "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.2", @@ -8374,7 +8125,6 @@ }, "node_modules/minimatch": { "version": "3.1.2", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -8394,7 +8144,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, "engines": { "node": ">=8" } @@ -8466,8 +8215,7 @@ "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "node_modules/mz": { "version": "2.7.0", @@ -8481,7 +8229,6 @@ }, "node_modules/natural-compare": { "version": "1.4.0", - "dev": true, "license": "MIT" }, "node_modules/natural-compare-lite": { @@ -8499,7 +8246,6 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "dev": true, "license": "MIT" }, "node_modules/no-case": { @@ -8527,8 +8273,7 @@ "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "dev": true + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" }, "node_modules/node-domexception": { "version": "1.0.0", @@ -8551,7 +8296,6 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, "dependencies": { "lodash": "^4.17.21" } @@ -8584,17 +8328,14 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "dev": true, "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.12", - "dev": true, "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8772,7 +8513,6 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -8795,7 +8535,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", - "dev": true, "dependencies": { "macos-release": "^2.5.0", "windows-release": "^4.0.0" @@ -8811,7 +8550,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8854,7 +8592,6 @@ }, "node_modules/p-try": { "version": "2.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -8867,7 +8604,6 @@ }, "node_modules/parent-module": { "version": "1.0.1", - "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -8878,7 +8614,6 @@ }, "node_modules/parse-json": { "version": "5.2.0", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", @@ -8920,7 +8655,6 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8928,7 +8662,6 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8936,7 +8669,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8944,14 +8676,12 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", - "dev": true, "dependencies": { "lru-cache": "^9.1.1 || ^10.0.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -8967,7 +8697,6 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", - "dev": true, "engines": { "node": "14 || >=16.14" } @@ -8976,7 +8705,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", - "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -8987,7 +8715,6 @@ }, "node_modules/path-type": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8995,12 +8722,10 @@ }, "node_modules/picocolors": { "version": "1.0.0", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -9011,7 +8736,6 @@ }, "node_modules/pirates": { "version": "4.0.5", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -9085,7 +8809,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, "engines": { "node": ">=4" } @@ -9126,7 +8849,6 @@ }, "node_modules/pretty-format": { "version": "29.5.0", - "dev": true, "license": "MIT", "dependencies": { "@jest/schemas": "^29.4.3", @@ -9139,7 +8861,6 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -9191,7 +8912,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -9199,7 +8919,6 @@ }, "node_modules/punycode": { "version": "2.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -9268,7 +8987,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -9317,7 +9035,6 @@ }, "node_modules/react-is": { "version": "18.2.0", - "dev": true, "license": "MIT" }, "node_modules/readable-stream": { @@ -9339,7 +9056,6 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -9352,7 +9068,6 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, "dependencies": { "resolve": "^1.1.6" }, @@ -9435,7 +9150,6 @@ "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, "engines": { "node": ">=0.10" } @@ -9451,14 +9165,12 @@ "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, "engines": { "node": ">=0.10.0" } }, "node_modules/resolve": { "version": "1.22.2", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.11.0", @@ -9500,7 +9212,6 @@ }, "node_modules/resolve-from": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9541,7 +9252,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -9554,7 +9264,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -9563,7 +9272,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -9697,7 +9405,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -9771,7 +9478,6 @@ "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, "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -9849,7 +9555,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", - "dev": true, "dependencies": { "randombytes": "^2.1.0" } @@ -9885,7 +9590,6 @@ }, "node_modules/shebang-command": { "version": "2.0.0", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -9896,7 +9600,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9906,7 +9609,6 @@ "version": "0.8.5", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -9933,7 +9635,6 @@ }, "node_modules/signal-exit": { "version": "3.0.7", - "dev": true, "license": "ISC" }, "node_modules/sisteransi": { @@ -9944,7 +9645,6 @@ }, "node_modules/slash": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -9961,7 +9661,6 @@ }, "node_modules/source-map": { "version": "0.6.1", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -9969,7 +9668,6 @@ }, "node_modules/source-map-support": { "version": "0.5.21", - "dev": true, "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -9990,12 +9688,10 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/stack-utils": { "version": "2.0.6", - "dev": true, "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" @@ -10006,7 +9702,6 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -10119,7 +9814,6 @@ }, "node_modules/strip-bom": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10159,7 +9853,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10177,7 +9870,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", - "dev": true, "engines": { "node": ">=0.10" } @@ -10211,7 +9903,6 @@ }, "node_modules/tapable": { "version": "2.2.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10221,7 +9912,6 @@ "version": "5.19.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", - "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -10239,7 +9929,6 @@ "version": "5.3.9", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", @@ -10273,7 +9962,6 @@ "version": "0.3.19", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -10283,7 +9971,6 @@ "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, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -10297,7 +9984,6 @@ "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" }, @@ -10311,12 +9997,10 @@ "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 + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/test-exclude": { "version": "6.0.0", - "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", @@ -10354,8 +10038,7 @@ "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "node_modules/time-constants": { "version": "1.0.3", @@ -10376,7 +10059,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -10386,12 +10068,10 @@ }, "node_modules/tmpl": { "version": "1.0.5", - "dev": true, "license": "BSD-3-Clause" }, "node_modules/to-fast-properties": { "version": "2.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10399,7 +10079,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -10421,7 +10100,6 @@ }, "node_modules/tree-kill": { "version": "1.2.2", - "dev": true, "license": "MIT", "bin": { "tree-kill": "cli.js" @@ -10579,7 +10257,6 @@ }, "node_modules/tsconfig-paths": { "version": "4.2.0", - "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -10594,7 +10271,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", - "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", @@ -10648,7 +10324,6 @@ }, "node_modules/type-detect": { "version": "4.0.8", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10853,7 +10528,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", - "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10902,7 +10576,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, "engines": { "node": ">= 10.0.0" } @@ -10924,7 +10597,6 @@ }, "node_modules/update-browserslist-db": { "version": "1.0.11", - "dev": true, "funding": [ { "type": "opencollective", @@ -10961,7 +10633,6 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -11057,7 +10728,6 @@ }, "node_modules/walker": { "version": "1.0.8", - "dev": true, "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" @@ -11067,7 +10737,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -11080,7 +10749,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, "dependencies": { "defaults": "^1.0.3" } @@ -11100,7 +10768,6 @@ "version": "5.88.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", - "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -11147,7 +10814,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", - "dev": true, "engines": { "node": ">=6" } @@ -11156,7 +10822,6 @@ "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, "engines": { "node": ">=10.13.0" } @@ -11171,7 +10836,6 @@ }, "node_modules/which": { "version": "2.0.2", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -11221,7 +10885,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", - "dev": true, "dependencies": { "execa": "^4.0.2" }, @@ -11236,7 +10899,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -11259,7 +10921,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, "dependencies": { "pump": "^3.0.0" }, @@ -11274,7 +10935,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, "engines": { "node": ">=8.12.0" } @@ -11283,7 +10943,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, "engines": { "node": ">=8" }, @@ -11295,7 +10954,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -11304,7 +10962,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, "dependencies": { "path-key": "^3.0.0" }, @@ -11316,7 +10973,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -11331,7 +10987,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, "engines": { "node": ">=6" } @@ -11362,7 +11017,6 @@ }, "node_modules/write-file-atomic": { "version": "4.0.2", - "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", @@ -11413,7 +11067,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 5505a040..38f9869f 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -31,6 +31,7 @@ }, "homepage": "https://github.com/AmplicaLabs/content-publishing-service#readme", "dependencies": { + "@jest/globals": "^29.5.0", "@frequency-chain/api-augment": "1.7.0", "@liaoliaots/nestjs-redis": "^9.0.5", "@nestjs/axios": "^2.0.0", @@ -63,7 +64,6 @@ "time-constants": "^1.0.3" }, "devDependencies": { - "@jest/globals": "^29.5.0", "@polkadot/typegen": "10.9.1", "@types/jest": "^29.5.2", "@types/time-constants": "^1.0.0", From da2cd74026a28348490002e4f0de734b3991f343 Mon Sep 17 00:00:00 2001 From: Aramik Date: Tue, 29 Aug 2023 10:36:44 -0700 Subject: [PATCH 007/137] Added api validation (#17) * Added api validation * added audio mimes * added tests --- .../apps/api/src/api.controller.ts | 47 +- .../content-watcher/apps/api/src/metadata.ts | 2 +- .../apps/api/test/app.e2e-spec.ts | 549 +++++++ .../apps/api/test/jest-e2e.json | 9 + .../worker/src/publisher/ipfs.publisher.ts | 25 +- services/content-watcher/jest.config.js | 7 - .../libs/common/src/constants.ts | 32 + .../libs/common/src/dtos/activity.dto.ts | 115 +- .../libs/common/src/dtos/announcement.dto.ts | 30 +- .../libs/common/src/dtos/common.dto.ts | 13 + services/content-watcher/package-lock.json | 1411 +++++++++-------- services/content-watcher/package.json | 42 +- 12 files changed, 1616 insertions(+), 666 deletions(-) create mode 100644 services/content-watcher/apps/api/test/app.e2e-spec.ts create mode 100644 services/content-watcher/apps/api/test/jest-e2e.json delete mode 100644 services/content-watcher/jest.config.js diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 369d2916..a32d30ac 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,8 +1,20 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Logger, Param, Post, Put, UploadedFile, UploadedFiles, UseInterceptors } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Logger, Param, ParseFilePipeBuilder, Post, Put, UploadedFiles, UseInterceptors } from '@nestjs/common'; import { v4 as uuidv4 } from 'uuid'; import { FilesInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes } from '@nestjs/swagger'; -import { BroadcastDto, ReactionDto, ReplyDto, UpdateDto, ProfileDto, AnnouncementResponseDto, FilesUploadDto, UploadResponseDto } from '../../../libs/common/src'; +import { + BroadcastDto, + ReactionDto, + ReplyDto, + UpdateDto, + ProfileDto, + AnnouncementResponseDto, + FilesUploadDto, + UploadResponseDto, + DsnpUserIdParam, + DsnpContentHashParam, +} from '../../../libs/common/src'; +import { DSNP_VALID_MIME_TYPES } from '../../../libs/common/src/constants'; @Controller('api') export class ApiController { @@ -28,8 +40,23 @@ export class ApiController { description: 'Asset files', type: FilesUploadDto, }) - // eslint-disable-next-line no-undef - async assetUpload(@UploadedFiles() files: Array): Promise { + async assetUpload( + @UploadedFiles( + new ParseFilePipeBuilder() + .addFileTypeValidator({ + fileType: DSNP_VALID_MIME_TYPES, + }) + .addMaxSizeValidator({ + // this is in bytes (2 GB) + maxSize: 2 * 1000 * 1000 * 1000, + }) + .build({ + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + files: // eslint-disable-next-line no-undef + Array, + ): Promise { this.logger.log(`upload ${files.length}`); return { assetIds: files.map((_) => uuidv4()), @@ -38,7 +65,7 @@ export class ApiController { @Post('content/:userDsnpId/broadcast') @HttpCode(202) - async broadcast(@Param('userDsnpId') userDsnpId: string, @Body() broadcastDto: BroadcastDto): Promise { + async broadcast(@Param() userDsnpId: DsnpUserIdParam, @Body() broadcastDto: BroadcastDto): Promise { this.logger.log(`broadcast ${userDsnpId}`); return { referenceId: uuidv4(), @@ -47,7 +74,7 @@ export class ApiController { @Post('content/:userDsnpId/reply') @HttpCode(202) - async reply(@Param('userDsnpId') userDsnpId: string, @Body() replyDto: ReplyDto): Promise { + async reply(@Param() userDsnpId: DsnpUserIdParam, @Body() replyDto: ReplyDto): Promise { this.logger.log(`reply ${userDsnpId}`); return { referenceId: uuidv4(), @@ -56,7 +83,7 @@ export class ApiController { @Post('content/:userDsnpId/reaction') @HttpCode(202) - async reaction(@Param('userDsnpId') userDsnpId: string, @Body() reactionDto: ReactionDto): Promise { + async reaction(@Param() userDsnpId: DsnpUserIdParam, @Body() reactionDto: ReactionDto): Promise { this.logger.log(`reaction ${userDsnpId}`); return { referenceId: uuidv4(), @@ -65,7 +92,7 @@ export class ApiController { @Put('content/:userDsnpId/:targetContentHash') @HttpCode(202) - async update(@Param('userDsnpId') userDsnpId: string, @Param('targetContentHash') targetContentHash: string, @Body() updateDto: UpdateDto): Promise { + async update(@Param() userDsnpId: DsnpUserIdParam, @Param() targetContentHash: DsnpContentHashParam, @Body() updateDto: UpdateDto): Promise { this.logger.log(`update ${userDsnpId}/${targetContentHash}`); return { referenceId: uuidv4(), @@ -74,7 +101,7 @@ export class ApiController { @Delete('content/:userDsnpId/:targetContentHash') @HttpCode(202) - async delete(@Param('userDsnpId') userDsnpId: string, @Param('targetContentHash') targetContentHash: string): Promise { + async delete(@Param() userDsnpId: DsnpUserIdParam, @Param() targetContentHash: DsnpContentHashParam): Promise { this.logger.log(`delete ${userDsnpId}/${targetContentHash}`); return { referenceId: uuidv4(), @@ -83,7 +110,7 @@ export class ApiController { @Put('profile/:userDsnpId') @HttpCode(202) - async profile(@Param('userDsnpId') userDsnpId: string, @Body() profileDto: ProfileDto): Promise { + async profile(@Param() userDsnpId: DsnpUserIdParam, @Body() profileDto: ProfileDto): Promise { this.logger.log(`profile ${userDsnpId}`); return { referenceId: uuidv4(), diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index 91a614a2..71c2be77 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -5,5 +5,5 @@ export default async () => { ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), ["../../../libs/common/src/dtos/common.dto"]: await import("../../../libs/common/src/dtos/common.dto") }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/activity.dto"), { "LinkDto": { href: { required: true, type: () => String }, name: { required: false, type: () => String } }, "LocationDto": { name: { required: true, type: () => String }, accuracy: { required: false, type: () => Number }, altitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String }, height: { required: false, type: () => Number }, width: { required: false, type: () => Number }, duration: { required: false, type: () => String } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String }, mentionedId: { required: false, type: () => String } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: true, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String }, href: { required: false, type: () => String } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String }, published: { required: true, type: () => String }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "UpdateDto": { targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].AnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String }, apply: { required: true, type: () => Number }, inReplyTo: { required: true, type: () => String } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/common.dto"), { "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }]] } }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "UpdateDto": { targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].AnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "DsnpContentHashParam": { targetContentHash: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }]] } }; }; \ No newline at end of file diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts new file mode 100644 index 00000000..b6786248 --- /dev/null +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -0,0 +1,549 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable no-undef */ +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import request from 'supertest'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ApiModule } from '../src/api.module'; + +describe('AppController E2E request verification!', () => { + let app: INestApplication; + let module: TestingModule; + const validlocation = { + name: 'name of location', + accuracy: 97, + altitude: 10, + latitude: 37.26, + longitude: -119.59, + radius: 10, + units: 'm', + }; + const validTags = [ + { + type: 'mention', + mentionedId: 'dsnp://78187493520', + }, + { + type: 'hashtag', + name: '#taggedUser', + }, + ]; + const validContent = { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + assets: [ + { + type: 'image', + name: 'image asset', + references: [ + { + referenceId: 'reference-id-1', + height: 123, + width: 321, + }, + ], + }, + { + type: 'link', + name: 'link asset', + href: 'http://example.com', + }, + ], + name: 'name of note content', + tag: validTags, + location: validlocation, + }; + const validBroadCast = { + content: validContent, + }; + const validReply = { + content: validContent, + inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }; + const validReaction = { + emoji: '🤌🏼', + apply: 5, + inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }; + const validProfile = { + icon: [ + { + referenceId: 'reference-id-1', + height: 123, + width: 321, + }, + ], + summary: 'profile summary', + published: '1970-01-01T00:00:00+00:00', + name: 'name of profile content', + tag: validTags, + location: validlocation, + }; + + beforeEach(async () => { + module = await Test.createTestingModule({ + imports: [ApiModule], + }).compile(); + + app = module.createNestApplication(); + const eventEmitter = app.get(EventEmitter2); + eventEmitter.on('shutdown', async () => { + await app.close(); + }); + app.useGlobalPipes(new ValidationPipe()); + app.enableShutdownHooks(); + await app.init(); + }); + + it('(GET) /api/health', () => request(app.getHttpServer()).get('/api/health').expect(200).expect({ status: 200 })); + + describe('Validate Route params', () => { + it('invalid userDsnpId should fail', async () => { + const invalidDsnpUserId = '2gsjhdaj'; + return request(app.getHttpServer()) + .post(`/api/content/${invalidDsnpUserId}/broadcast`) + .send(validBroadCast) + .expect(400) + .expect((res) => expect(res.text).toContain('must be a number string')); + }); + + it('invalid DsnpContentHashParam should fail', () => { + const invalidContentHashParam = '2gsjhdaj'; + return request(app.getHttpServer()) + .delete(`/api/content/123/${invalidContentHashParam}`) + .expect(400) + .expect((res) => expect(res.text).toContain('must be in hexadecimal format')); + }); + }); + + describe('(POST) /api/content/:dsnpUserId/broadcast', () => { + it('valid request should work!', () => + request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(validBroadCast) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + + it('empty body should fail', () => { + const body = {}; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('should not be empty')); + }); + + it('empty content should fail', () => { + const body = { + content: { + content: '', + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('content must be longer than or equal to 1 characters')); + }); + + it('empty published should fail', () => { + const body = { + content: { + content: 'tests content', + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('published must match')); + }); + + it('invalid published should fail', () => { + const body2 = { + content: { + content: 'tests content', + published: '1980', + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body2) + .expect(400) + .expect((res) => expect(res.text).toContain('published must match')); + }); + + it('invalid assets type should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + assets: [ + { + type: 'invalid', + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('type must be one of the following values')); + }); + + it('image asset without references should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + assets: [ + { + type: 'image', + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('references should not be empty')); + }); + + it('image asset with non unique references id should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + assets: [ + { + type: 'image', + references: [ + { + referenceId: 'reference-id-1', + }, + { + referenceId: 'reference-id-1', + }, + ], + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('elements must be unique')); + }); + + it('link asset without href should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + assets: [ + { + type: 'link', + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('href must be longer than or equal to 1 character')); + }); + + it('link asset with invalid href protocol should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + assets: [ + { + type: 'link', + href: 'ftp://sgdjas8912yejc.com', + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('href must be a URL address')); + }); + + it('hashtag without name should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + tag: [ + { + type: 'hashtag', + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('name must be longer than or equal to 1 character')); + }); + + it('mentioned tag without mentionedId should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + tag: [ + { + type: 'mention', + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('mentionedId must be longer than or equal to 1 character')); + }); + + it('invalid tag type should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + tag: [ + { + type: 'invalid', + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('type must be one of the following values')); + }); + + it('location with invalid units should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + location: { + name: 'name of location', + units: 'invalid', + }, + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('units must be one of the following values')); + }); + + it('location with empty name should fail', () => { + const body = { + content: { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + location: { + name: '', + }, + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('name must be longer than or equal to 1 characters')); + }); + }); + + describe('(POST) /api/content/:dsnpUserId/reply', () => { + it('valid request should work!', () => + request(app.getHttpServer()) + .post(`/api/content/123/reply`) + .send(validReply) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + + it('empty body should fail', () => { + const body = {}; + return request(app.getHttpServer()) + .post(`/api/content/123/reply`) + .send(body) + .expect(400) + .expect((res) => expect(res.text).toContain('should not be empty')); + }); + + it('empty inReplyTo should fail', () => + request(app.getHttpServer()) + .post(`/api/content/123/reply`) + .send({ + content: validContent, + }) + .expect(400) + .expect((res) => expect(res.text).toContain('inReplyTo must be a string'))); + + it('invalid inReplyTo should fail', () => + request(app.getHttpServer()) + .post(`/api/content/123/reply`) + .send({ + content: validContent, + inReplyTo: 'shgdjas72gsjajasa', + }) + .expect(400) + .expect((res) => expect(res.text).toContain('inReplyTo must match'))); + }); + + describe('(POST) /api/content/:dsnpUserId/reaction', () => { + it('valid request should work!', () => + request(app.getHttpServer()) + .post(`/api/content/123/reaction`) + .send(validReaction) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + + it('valid emoji should fail', () => + request(app.getHttpServer()) + .post(`/api/content/123/reaction`) + .send({ + emoji: '2', + }) + .expect(400) + .expect((res) => expect(res.text).toContain('emoji must match'))); + + it('valid apply amount should fail', () => + request(app.getHttpServer()) + .post(`/api/content/123/reaction`) + .send({ + emoji: '😀', + apply: -1, + }) + .expect(400) + .expect((res) => expect(res.text).toContain('apply must not be less than 0'))); + + it('invalid inReplyTo should fail', () => + request(app.getHttpServer()) + .post(`/api/content/123/reaction`) + .send({ + emoji: '😀', + apply: 0, + inReplyTo: 'shgdjas72gsjajasa', + }) + .expect(400) + .expect((res) => expect(res.text).toContain('inReplyTo must match'))); + }); + + describe('(PUT) /api/content/:dsnpUserId/:contentHash', () => { + it('valid request should work!', () => + request(app.getHttpServer()) + .put(`/api/content/123/0x7653423447AF`) + .send({ + targetAnnouncementType: 'broadcast', + content: validContent, + }) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + + it('invalid targetAnnouncementType should fail', () => + request(app.getHttpServer()) + .put(`/api/content/123/0x7653423447AF`) + .send({ + targetAnnouncementType: 'invalid', + content: validContent, + }) + .expect(400) + .expect((res) => expect(res.text).toContain('targetAnnouncementType must be one of the following values'))); + }); + + describe('(DELETE) /api/content/:dsnpUserId/contentHash', () => { + it('valid request should work!', () => + request(app.getHttpServer()) + .delete(`/api/content/123/0x7653423447AF`) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + }); + + describe('(PUT) /api/profile/:userDsnpId', () => { + it('valid request should work!', () => + request(app.getHttpServer()) + .put(`/api/profile/123`) + .send({ + profile: validProfile, + }) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + + it('empty profile should fail', () => + request(app.getHttpServer()) + .put(`/api/profile/123`) + .send({}) + .expect(400) + .expect((res) => expect(res.text).toContain('should not be empty'))); + + it('invalid published should fail', () => + request(app.getHttpServer()) + .put(`/api/profile/123`) + .send({ + profile: { + published: '1980', + }, + }) + .expect(400) + .expect((res) => expect(res.text).toContain('published must match'))); + + it('non unique reference ids should fail', () => + request(app.getHttpServer()) + .put(`/api/profile/123`) + .send({ + profile: { + icon: [ + { + referenceId: 'reference-id-1', + }, + { + referenceId: 'reference-id-1', + }, + ], + }, + }) + .expect(400) + .expect((res) => expect(res.text).toContain('elements must be unique'))); + }); + + describe('(PUT) /api/asset/upload', () => { + it('valid request should work!', () => + request(app.getHttpServer()) + .put(`/api/asset/upload`) + .attach('files', Buffer.from(validContent.toString()), 'image.jpg') + .expect(202) + .expect((res) => expect(res.text).toContain('assetIds'))); + + it('invalid mime should fail', () => + request(app.getHttpServer()) + .put(`/api/asset/upload`) + .attach('files', Buffer.from(validContent.toString()), 'doc.txt') + .expect(422) + .expect((res) => expect(res.text).toContain('expected type is'))); + }); + + afterEach(async () => { + try { + await app.close(); + } catch (err) { + console.error(err); + } + }); +}); diff --git a/services/content-watcher/apps/api/test/jest-e2e.json b/services/content-watcher/apps/api/test/jest-e2e.json new file mode 100644 index 00000000..e9d912f3 --- /dev/null +++ b/services/content-watcher/apps/api/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts index 726cd033..064c77fd 100644 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -12,7 +12,11 @@ import { createKeys } from '../blockchain/create-keys'; export class IPFSPublisher { private logger: Logger; - constructor(private configService: ConfigService, private blockchainService: BlockchainService, private eventEmitter: EventEmitter2) { + constructor( + private configService: ConfigService, + private blockchainService: BlockchainService, + private eventEmitter: EventEmitter2, + ) { this.logger = new Logger(IPFSPublisher.name); } @@ -47,14 +51,17 @@ export class IPFSPublisher { }); const totalCapUsedPerEpoch = await Promise.all(batchPromises); - const totalCapacityUsed = totalCapUsedPerEpoch.reduce((acc, curr) => { - const epoch = Object.keys(curr)[0]; - if (acc[epoch]) { - acc[epoch] += curr[epoch]; - } - acc[epoch] = curr[epoch]; - return acc; - }, {} as { [key: string]: bigint }); + const totalCapacityUsed = totalCapUsedPerEpoch.reduce( + (acc, curr) => { + const epoch = Object.keys(curr)[0]; + if (acc[epoch]) { + acc[epoch] += curr[epoch]; + } + acc[epoch] = curr[epoch]; + return acc; + }, + {} as { [key: string]: bigint }, + ); this.logger.debug(`Total capacity used: ${JSON.stringify(totalCapacityUsed)}`); return totalCapacityUsed; diff --git a/services/content-watcher/jest.config.js b/services/content-watcher/jest.config.js deleted file mode 100644 index 7eb5e95a..00000000 --- a/services/content-watcher/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - setupFiles: ["dotenv/config"], - testPathIgnorePatterns: ['/dist'] -}; diff --git a/services/content-watcher/libs/common/src/constants.ts b/services/content-watcher/libs/common/src/constants.ts index a7df8f39..94798661 100644 --- a/services/content-watcher/libs/common/src/constants.ts +++ b/services/content-watcher/libs/common/src/constants.ts @@ -1,2 +1,34 @@ +/** + * Regex for ISO 8601 + * - T separation + * - Required Time + * - Supports fractional seconds + * - Z or hour minute offset + */ +export const ISO8601_REGEX = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,})?(Z|[+-][01][0-9]:[0-5][0-9])?$/; +/** + * DSNP user URI based on DSNP Spec + * example: dsnp://78187493520 + */ +export const DSNP_USER_URI_REGEX = /^dsnp:\/\/[1-9][0-9]{0,19}$/i; +/** + * DSNP content URI based on DSNP Spec + * example: dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef + */ +export const DSNP_CONTENT_URI_REGEX = /^dsnp:\/\/[1-9][0-9]{0,19}\/0x[0-9a-f]+$/i; +/** + * DSNP character ranges for valid emojis + */ +export const DSNP_EMOJI_REGEX = /^[\u{2000}-\u{2BFF}\u{E000}-\u{FFFF}\u{1F000}-\u{FFFFF}]+$/u; +/** + * Activity Stream Duration based on https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration + */ +export const DURATION_REGEX = /^-?P(([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(\.[0-9]+)?S)?)?)+$/; + +/** + * Dsnp attachments mime types + */ +export const DSNP_VALID_MIME_TYPES = + /(image\/jpeg|image\/png|image\/svg\+xml|image\/webp|image\/gif|video\/mpeg|video\/ogg|video\/webm|video\/H256|video\/mp4|audio\/mpeg|audio\/ogg|audio\/webm)$/; export const SECONDS_PER_BLOCK = 12; export const CAPACITY_EPOCH_TIMEOUT_NAME = 'capacity-epoch-timeout'; diff --git a/services/content-watcher/libs/common/src/dtos/activity.dto.ts b/services/content-watcher/libs/common/src/dtos/activity.dto.ts index 1b37f27a..34cd367f 100644 --- a/services/content-watcher/libs/common/src/dtos/activity.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/activity.dto.ts @@ -1,7 +1,30 @@ /** * File name should always end with `.dto.ts` for swagger metadata generator to get picked up */ -// eslint-disable-next-line no-shadow,max-classes-per-file +// eslint-disable-next-line max-classes-per-file +import { + ArrayNotEmpty, + ArrayUnique, + IsArray, + IsEnum, + IsLatitude, + IsLongitude, + IsNumber, + IsOptional, + IsPositive, + IsString, + IsUrl, + Matches, + Max, + Min, + MinLength, + ValidateIf, + ValidateNested, +} from 'class-validator'; +import { Type } from 'class-transformer'; +import { DSNP_USER_URI_REGEX, DURATION_REGEX, ISO8601_REGEX } from '../constants'; + +// eslint-disable-next-line no-shadow export enum UnitTypeDto { CM = 'cm', M = 'm', @@ -25,74 +48,148 @@ export enum AttachmentTypeDto { VIDEO = 'video', } -export class LinkDto { - href: string; - - name?: string; -} - export class LocationDto { + @MinLength(1) + @IsString() name: string; + @IsOptional() + @IsNumber() + @Min(0) + @Max(100) accuracy?: number; + @IsOptional() + @IsNumber() altitude?: number; + @IsOptional() + @IsNumber() + @IsLatitude() + latitude?: number; + + @IsOptional() + @IsNumber() + @IsLongitude() longitude?: number; + @IsOptional() + @IsNumber() + @Min(0) radius?: number; + @IsOptional() + @IsEnum(UnitTypeDto) units?: UnitTypeDto; } export class AssetReferenceDto { + @MinLength(1) + @IsString() referenceId: string; + @IsOptional() + @IsNumber() + @IsPositive() height?: number; + @IsOptional() + @IsNumber() + @IsPositive() width?: number; + @IsOptional() + @IsString() + @Matches(DURATION_REGEX) duration?: string; } export class TagDto { + @IsEnum(TagTypeDto) type: TagTypeDto; + @ValidateIf((o) => o.type === TagTypeDto.Hashtag) + @MinLength(1) + @IsString() name?: string; + @ValidateIf((o) => o.type === TagTypeDto.Mention) + @MinLength(1) + @IsString() + @Matches(DSNP_USER_URI_REGEX) mentionedId?: string; } export class AssetDto { + @IsEnum(AttachmentTypeDto) type: AttachmentTypeDto; - references: Array; - + @ValidateIf((o) => o.type !== AttachmentTypeDto.LINK) + @ValidateNested({ each: true }) + @IsArray() + @ArrayNotEmpty() + @ArrayUnique((o) => o.referenceId) + @Type(() => AssetReferenceDto) + references?: Array; + + @IsOptional() + @IsString() + @MinLength(1) name?: string; + @ValidateIf((o) => o.type === AttachmentTypeDto.LINK) + @IsString() + @MinLength(1) + @IsUrl({ protocols: ['http', 'https'] }) href?: string; } export class BaseActivityDto { + @IsOptional() name?: string; + @IsOptional() + @ValidateNested({ each: true }) + @IsArray() + @Type(() => TagDto) tag?: Array; + @IsOptional() + @ValidateNested() + @Type(() => LocationDto) location?: LocationDto; } export class NoteActivityDto extends BaseActivityDto { + @MinLength(1) + @IsString() content: string; + @IsString() + @Matches(ISO8601_REGEX) published: string; + @IsOptional() + @ValidateNested({ each: true }) + @IsArray() + @Type(() => AssetDto) assets?: Array; } export class ProfileActivityDto extends BaseActivityDto { + @IsOptional() + @ValidateNested({ each: true }) + @IsArray() + @ArrayUnique((o) => o.referenceId) + @Type(() => AssetReferenceDto) icon?: Array; + @IsOptional() + @IsString() summary?: string; + @IsOptional() + @IsString() + @Matches(ISO8601_REGEX) published?: string; } diff --git a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts index f684c4e1..8466d931 100644 --- a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts @@ -2,42 +2,64 @@ * File name should always end with `.dto.ts` for swagger metadata generator to get picked up */ // eslint-disable-next-line max-classes-per-file +import { IsEnum, IsInt, IsNotEmpty, IsString, Matches, Max, MaxLength, Min, MinLength, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; import { NoteActivityDto, ProfileActivityDto } from './activity.dto'; +import { DSNP_CONTENT_URI_REGEX, DSNP_EMOJI_REGEX } from '../constants'; // eslint-disable-next-line no-shadow export enum AnnouncementTypeDto { - TOMBSTONE = 'tombstone', BROADCAST = 'broadcast', REPLY = 'reply', - REACTION = 'reaction', - PROFILE = 'profile', - UPDATE = 'update', } export class BroadcastDto { + @IsNotEmpty() + @ValidateNested() + @Type(() => NoteActivityDto) content: NoteActivityDto; } export class ReplyDto { + @IsString() + @Matches(DSNP_CONTENT_URI_REGEX) inReplyTo: string; + @IsNotEmpty() + @ValidateNested() + @Type(() => NoteActivityDto) content: NoteActivityDto; } export class UpdateDto { + @IsEnum(AnnouncementTypeDto) targetAnnouncementType: AnnouncementTypeDto; + @IsNotEmpty() + @ValidateNested() + @Type(() => NoteActivityDto) content: NoteActivityDto; } export class ReactionDto { + @MinLength(1) + @IsString() + @Matches(DSNP_EMOJI_REGEX) emoji: string; + @IsInt() + @Min(0) + @Max(255) apply: number; + @IsString() + @Matches(DSNP_CONTENT_URI_REGEX) inReplyTo: string; } export class ProfileDto { + @IsNotEmpty() + @ValidateNested() + @Type(() => ProfileActivityDto) profile: ProfileActivityDto; } diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index 9fd836aa..6b325f1e 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -3,6 +3,19 @@ */ // eslint-disable-next-line max-classes-per-file import { ApiProperty } from '@nestjs/swagger'; +import { IsHexadecimal, IsNotEmpty, IsNumberString } from 'class-validator'; + +export class DsnpUserIdParam { + @IsNotEmpty() + @IsNumberString({ no_symbols: true }) + userDsnpId: string; +} + +export class DsnpContentHashParam { + @IsNotEmpty() + @IsHexadecimal({ message: 'targetContentHash must be in hexadecimal format!' }) + targetContentHash: string; +} export class AnnouncementResponseDto { referenceId: string; diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index ef07ccf5..1bc6bcab 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -43,7 +43,10 @@ }, "devDependencies": { "@polkadot/typegen": "10.9.1", + "@types/express": "^4.17.17", "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@types/supertest": "^2.0.12", "@types/time-constants": "^1.0.0", "@typescript-eslint/parser": "^5.59.8", "@typescript-eslint/typescript-estree": "5.59.8", @@ -56,10 +59,12 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.7.0", "eslint-plugin-nestjs": "^1.2.3", - "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", "license-report": "^6.4.0", + "supertest": "^6.3.3", "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", @@ -668,11 +673,12 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.3", - "license": "MIT", + "version": "7.22.11", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", + "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", "peer": true, "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -730,8 +736,9 @@ }, "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", "peer": true }, "node_modules/@colors/colors": { @@ -951,16 +958,17 @@ } }, "node_modules/@jest/console": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.4.tgz", + "integrity": "sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3", "slash": "^3.0.0" }, "engines": { @@ -968,37 +976,38 @@ } }, "node_modules/@jest/core": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.4.tgz", + "integrity": "sha512-U/vq5ccNTSVgYH7mHnodHmCffGWHJnz/E1BEWlLuK5pM4FZmGfBn/nrJGLjUsSmyx3otCeqc1T31F4y08AMDLg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.6.4", + "@jest/reporters": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", + "jest-changed-files": "^29.6.3", + "jest-config": "^29.6.4", + "jest-haste-map": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.6.4", + "jest-resolve-dependencies": "^29.6.4", + "jest-runner": "^29.6.4", + "jest-runtime": "^29.6.4", + "jest-snapshot": "^29.6.4", + "jest-util": "^29.6.3", + "jest-validate": "^29.6.3", + "jest-watcher": "^29.6.4", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.6.3", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -1015,79 +1024,85 @@ } }, "node_modules/@jest/environment": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.4.tgz", + "integrity": "sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ==", "dependencies": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/fake-timers": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0" + "jest-mock": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.4.tgz", + "integrity": "sha512-Warhsa7d23+3X5bLbrbYvaehcgX5TLYhI03JKoedTiI8uJU4IhqYBWF7OSSgUyz4IgLpUYPkK0AehA5/fRclAA==", "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" + "expect": "^29.6.4", + "jest-snapshot": "^29.6.4" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.4.tgz", + "integrity": "sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg==", "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.4.tgz", + "integrity": "sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw==", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-message-util": "^29.6.3", + "jest-mock": "^29.6.3", + "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.4.tgz", + "integrity": "sha512-wVIn5bdtjlChhXAzVXavcY/3PEjf4VqM174BM3eGL5kMxLiZD5CLnbmkEyA1Dwh9q8XjP6E8RwjBsY/iCWrWsA==", "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" + "@jest/environment": "^29.6.4", + "@jest/expect": "^29.6.4", + "@jest/types": "^29.6.3", + "jest-mock": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.4.tgz", + "integrity": "sha512-sxUjWxm7QdchdrD3NfWKrL8FBsortZeibSJv4XLjESOOjSUOkjQcb0ZHJwfhEGIvBvTluTzfG2yZWZhkrXJu8g==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -1095,13 +1110,13 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3", + "jest-worker": "^29.6.4", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1119,48 +1134,53 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", + "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, - "license": "MIT", "peer": true, - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jest/reporters/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", + "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" } }, "node_modules/@jest/schemas": { - "version": "29.4.3", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.4.3", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -1168,39 +1188,26 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/source-map/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jest/source-map/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jest/test-result": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.4.tgz", + "integrity": "sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.6.4", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1209,14 +1216,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.4.tgz", + "integrity": "sha512-E84M6LbpcRq3fT4ckfKs9ryVanwkaIB0Ws9bw3/yP4seRLg/VaCZ/LgW0MCq5wwk4/iP/qnilD41aj2fsw2RMg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/test-result": "^29.5.0", + "@jest/test-result": "^29.6.4", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.6.4", "slash": "^3.0.0" }, "engines": { @@ -1224,20 +1232,21 @@ } }, "node_modules/@jest/transform": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.4.tgz", + "integrity": "sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA==", "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.6.4", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.6.3", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1267,10 +1276,11 @@ } }, "node_modules/@jest/types": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1780,7 +1790,8 @@ }, "node_modules/@nestjs/testing": { "version": "9.4.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.4.3.tgz", + "integrity": "sha512-LDT8Ai2eKnTzvnPaJwWOK03qTaFap5uHHsJCv6dL0uKWk6hyF9jms8DjyVaGsaujCaXDG8izl1mDEER0OmxaZA==", "dependencies": { "tslib": "2.5.3" }, @@ -2431,65 +2442,6 @@ "node": ">=16" } }, - "node_modules/@redis/bloom": { - "version": "1.2.0", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/client": { - "version": "1.5.8", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/graph": { - "version": "1.1.0", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/json": { - "version": "1.0.4", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/search": { - "version": "1.1.3", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/time-series": { - "version": "1.0.4", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, "node_modules/@scure/base": { "version": "1.1.1", "funding": [ @@ -2516,8 +2468,9 @@ "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "license": "MIT" + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, "node_modules/@sindresorhus/is": { "version": "5.4.1", @@ -2532,21 +2485,24 @@ }, "node_modules/@sinonjs/commons": { "version": "3.0.0", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.1.0", - "license": "BSD-3-Clause", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "node_modules/@sqltools/formatter": { "version": "1.2.5", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", "peer": true }, "node_modules/@substrate/connect": { @@ -2606,8 +2562,9 @@ }, "node_modules/@types/babel__core": { "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -2619,8 +2576,9 @@ }, "node_modules/@types/babel__generator": { "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@babel/types": "^7.0.0" @@ -2628,8 +2586,9 @@ }, "node_modules/@types/babel__template": { "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -2638,7 +2597,10 @@ }, "node_modules/@types/babel__traverse": { "version": "7.20.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "dev": true, + "peer": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -2667,6 +2629,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.44.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", @@ -2714,7 +2682,8 @@ }, "node_modules/@types/graceful-fs": { "version": "4.1.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", "dependencies": { "@types/node": "*" } @@ -2779,18 +2748,15 @@ } }, "node_modules/@types/node": { - "version": "20.2.5", - "license": "MIT" + "version": "20.5.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", + "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "license": "MIT" - }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -2802,9 +2768,10 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/semver": { - "version": "7.5.0", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/@types/send": { @@ -2840,6 +2807,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/superagent": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.18.tgz", + "integrity": "sha512-LOWgpacIV8GHhrsQU+QMZuomfqXiqzz3ILLkCtKx3Us6AmomFViuzKT9D693QTKgyut2oCytMG8/efOop+DB+w==", + "dev": true, + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/supertest": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz", + "integrity": "sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ==", + "dev": true, + "dependencies": { + "@types/superagent": "*" + } + }, "node_modules/@types/time-constants": { "version": "1.0.0", "dev": true, @@ -2866,17 +2852,18 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/type-utils": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", @@ -2900,13 +2887,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2917,9 +2905,10 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2930,12 +2919,13 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3015,13 +3005,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3042,9 +3033,10 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3055,13 +3047,14 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, - "license": "BSD-2-Clause", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3082,12 +3075,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3165,17 +3159,18 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -3191,13 +3186,14 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11" + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3208,9 +3204,10 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3221,13 +3218,14 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", "dev": true, - "license": "BSD-2-Clause", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3248,12 +3246,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/types": "5.62.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3577,7 +3576,8 @@ }, "node_modules/any-promise": { "version": "1.3.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "peer": true }, "node_modules/anymatch": { @@ -3593,7 +3593,8 @@ }, "node_modules/app-root-path": { "version": "3.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", "peer": true, "engines": { "node": ">= 6.0.0" @@ -3693,11 +3694,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/async": { - "version": "3.2.3", - "license": "MIT", - "optional": true, - "peer": true + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true }, "node_modules/asynckit": { "version": "0.4.0", @@ -3724,15 +3725,16 @@ } }, "node_modules/babel-jest": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.4.tgz", + "integrity": "sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/transform": "^29.5.0", + "@jest/transform": "^29.6.4", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", + "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -3759,9 +3761,10 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@babel/template": "^7.3.3", @@ -3795,12 +3798,13 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.5.0", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", + "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -4002,13 +4006,16 @@ }, "node_modules/bser": { "version": "2.1.1", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dependencies": { "node-int64": "^0.4.0" } }, "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", @@ -4023,7 +4030,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "peer": true, "dependencies": { "base64-js": "^1.3.1", @@ -4120,26 +4126,6 @@ "node": ">= 0.8" } }, - "node_modules/cache-manager": { - "version": "4.1.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "async": "3.2.3", - "lodash.clonedeep": "^4.5.0", - "lru-cache": "^7.10.1" - } - }, - "node_modules/cache-manager/node_modules/lru-cache": { - "version": "7.18.3", - "license": "ISC", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - } - }, "node_modules/cacheable-lookup": { "version": "7.0.0", "dev": true, @@ -4224,8 +4210,9 @@ }, "node_modules/char-regex": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -4294,8 +4281,9 @@ }, "node_modules/cjs-module-lexer": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/class-transformer": { @@ -4324,7 +4312,8 @@ }, "node_modules/cli-highlight": { "version": "2.1.11", - "license": "ISC", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", "peer": true, "dependencies": { "chalk": "^4.0.0", @@ -4344,7 +4333,8 @@ }, "node_modules/cli-highlight/node_modules/cliui": { "version": "7.0.4", - "license": "ISC", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "peer": true, "dependencies": { "string-width": "^4.2.0", @@ -4354,7 +4344,8 @@ }, "node_modules/cli-highlight/node_modules/yargs": { "version": "16.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "peer": true, "dependencies": { "cliui": "^7.0.2", @@ -4371,7 +4362,8 @@ }, "node_modules/cli-highlight/node_modules/yargs-parser": { "version": "20.2.9", - "license": "ISC", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "peer": true, "engines": { "node": ">=10" @@ -4439,8 +4431,9 @@ }, "node_modules/co": { "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, - "license": "MIT", "peer": true, "engines": { "iojs": ">= 1.0.0", @@ -4448,9 +4441,10 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.1", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/color-convert": { @@ -4500,6 +4494,12 @@ "node": ">= 6" } }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "license": "MIT" @@ -4558,6 +4558,12 @@ "version": "1.0.6", "license": "MIT" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, "node_modules/core-util-is": { "version": "1.0.3", "license": "MIT" @@ -4631,7 +4637,8 @@ }, "node_modules/date-fns": { "version": "2.30.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "peer": true, "dependencies": { "@babel/runtime": "^7.21.0" @@ -4685,10 +4692,19 @@ } }, "node_modules/dedent": { - "version": "0.7.0", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", "dev": true, - "license": "MIT", - "peer": true + "peer": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } }, "node_modules/deep-extend": { "version": "0.6.0", @@ -4818,13 +4834,24 @@ }, "node_modules/detect-newline": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "devOptional": true, @@ -4834,8 +4861,9 @@ } }, "node_modules/diff-sequences": { - "version": "29.4.3", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -4897,8 +4925,9 @@ }, "node_modules/emittery": { "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=12" @@ -5421,20 +5450,29 @@ "license": "0BSD" }, "node_modules/eslint-plugin-prettier": { - "version": "4.2.1", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", + "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", "dev": true, - "license": "MIT", "dependencies": { - "prettier-linter-helpers": "^1.0.0" + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" }, "engines": { - "node": ">=12.0.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/prettier" }, "peerDependencies": { - "eslint": ">=7.28.0", - "prettier": ">=2.0.0" + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "prettier": ">=3.0.0" }, "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, "eslint-config-prettier": { "optional": true } @@ -5646,6 +5684,8 @@ }, "node_modules/exit": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "peer": true, "engines": { @@ -5653,14 +5693,15 @@ } }, "node_modules/expect": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.4.tgz", + "integrity": "sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA==", "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" + "@jest/expect-utils": "^29.6.4", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5827,7 +5868,8 @@ }, "node_modules/fb-watchman": { "version": "2.0.2", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dependencies": { "bser": "2.1.1" } @@ -6038,6 +6080,21 @@ "node": ">=12.20.0" } }, + "node_modules/formidable": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", + "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "dev": true, + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0", + "qs": "^6.11.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "license": "MIT", @@ -6074,6 +6131,19 @@ "version": "1.0.0", "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==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "license": "MIT" @@ -6103,15 +6173,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/generic-pool": { - "version": "3.9.0", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "license": "MIT", @@ -6311,12 +6372,6 @@ "version": "4.2.11", "license": "ISC" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/graphemer": { "version": "1.4.0", "dev": true, @@ -6420,9 +6475,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/highlight.js": { "version": "10.7.3", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "peer": true, "engines": { "node": "*" @@ -6430,8 +6495,9 @@ }, "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", "peer": true }, "node_modules/http-cache-semantics": { @@ -6525,8 +6591,9 @@ }, "node_modules/import-local": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "pkg-dir": "^4.2.0", @@ -6772,8 +6839,9 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -7021,23 +7089,25 @@ } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", + "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", "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "license": "BSD-3-Clause", "peer": true, "dependencies": { "debug": "^4.1.1", @@ -7049,9 +7119,10 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.5", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", "dev": true, - "license": "BSD-3-Clause", "peer": true, "dependencies": { "html-escaper": "^2.0.0", @@ -7069,15 +7140,16 @@ } }, "node_modules/jest": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.4.tgz", + "integrity": "sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.6.4", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.5.0" + "jest-cli": "^29.6.4" }, "bin": { "jest": "bin/jest.js" @@ -7095,12 +7167,14 @@ } }, "node_modules/jest-changed-files": { - "version": "29.5.0", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.6.3.tgz", + "integrity": "sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "execa": "^5.0.0", + "jest-util": "^29.6.3", "p-limit": "^3.1.0" }, "engines": { @@ -7109,8 +7183,9 @@ }, "node_modules/jest-changed-files/node_modules/execa": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "cross-spawn": "^7.0.3", @@ -7132,8 +7207,9 @@ }, "node_modules/jest-changed-files/node_modules/human-signals": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "Apache-2.0", "peer": true, "engines": { "node": ">=10.17.0" @@ -7141,8 +7217,9 @@ }, "node_modules/jest-changed-files/node_modules/is-stream": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=8" @@ -7153,8 +7230,9 @@ }, "node_modules/jest-changed-files/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", "peer": true, "engines": { "node": ">=6" @@ -7162,8 +7240,9 @@ }, "node_modules/jest-changed-files/node_modules/npm-run-path": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "path-key": "^3.0.0" @@ -7174,8 +7253,9 @@ }, "node_modules/jest-changed-files/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", "peer": true, "dependencies": { "mimic-fn": "^2.1.0" @@ -7189,36 +7269,38 @@ }, "node_modules/jest-changed-files/node_modules/strip-final-newline": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=6" } }, "node_modules/jest-circus": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.4.tgz", + "integrity": "sha512-YXNrRyntVUgDfZbjXWBMPslX1mQ8MrSG0oM/Y06j9EYubODIyHWP8hMUbjbZ19M3M+zamqEur7O80HODwACoJw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.6.4", + "@jest/expect": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-each": "^29.6.3", + "jest-matcher-utils": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-runtime": "^29.6.4", + "jest-snapshot": "^29.6.4", + "jest-util": "^29.6.3", "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.6.3", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -7228,21 +7310,22 @@ } }, "node_modules/jest-cli": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.4.tgz", + "integrity": "sha512-+uMCQ7oizMmh8ZwRfZzKIEszFY9ksjjEQnTEMTaL7fYiL3Kw4XhqT9bYh+A4DQKUb67hZn2KbtEnDuHvcgK4pQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-config": "^29.6.4", + "jest-util": "^29.6.3", + "jest-validate": "^29.6.3", "prompts": "^2.0.1", "yargs": "^17.3.1" }, @@ -7262,31 +7345,32 @@ } }, "node_modules/jest-config": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.4.tgz", + "integrity": "sha512-JWohr3i9m2cVpBumQFv2akMEnFEPVOh+9L2xIBJhJ0zOaci2ZXuKJj0tgMKQCBZAKA09H049IR4HVS/43Qb19A==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", + "@jest/test-sequencer": "^29.6.4", + "@jest/types": "^29.6.3", + "babel-jest": "^29.6.4", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-circus": "^29.6.4", + "jest-environment-node": "^29.6.4", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.6.4", + "jest-runner": "^29.6.4", + "jest-util": "^29.6.3", + "jest-validate": "^29.6.3", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.6.3", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -7307,22 +7391,24 @@ } }, "node_modules/jest-diff": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.4.tgz", + "integrity": "sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==", "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.4.3", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.6.3.tgz", + "integrity": "sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "detect-newline": "^3.0.0" @@ -7332,58 +7418,62 @@ } }, "node_modules/jest-each": { - "version": "29.5.0", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.3.tgz", + "integrity": "sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "jest-util": "^29.6.3", + "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.4.tgz", + "integrity": "sha512-i7SbpH2dEIFGNmxGCpSc2w9cA4qVD+wfvg2ZnfQ7XVrKL0NA5uDVBIiGH8SR4F0dKEv/0qI5r+aDomDf04DpEQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.6.4", + "@jest/fake-timers": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-mock": "^29.6.3", + "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.4.tgz", + "integrity": "sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog==", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.6.3", + "jest-worker": "^29.6.4", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -7395,42 +7485,45 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.5.0", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz", + "integrity": "sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz", + "integrity": "sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==", "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-diff": "^29.6.4", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz", + "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==", "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.6.3", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -7439,12 +7532,13 @@ } }, "node_modules/jest-mock": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.3.tgz", + "integrity": "sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.5.0" + "jest-util": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7452,8 +7546,9 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -7468,24 +7563,26 @@ } }, "node_modules/jest-regex-util": { - "version": "29.4.3", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.4.tgz", + "integrity": "sha512-fPRq+0vcxsuGlG0O3gyoqGTAxasagOxEuyoxHeyxaZbc9QNek0AmJWSkhjlMG+mTsj+8knc/mWb3fXlRNVih7Q==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.6.4", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-util": "^29.6.3", + "jest-validate": "^29.6.3", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -7495,43 +7592,45 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.4.tgz", + "integrity": "sha512-7+6eAmr1ZBF3vOAJVsfLj1QdqeXG+WYhidfLHBRZqGN24MFRIiKG20ItpLw2qRAsW/D2ZUUmCNf6irUr/v6KHA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.6.4" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.4.tgz", + "integrity": "sha512-SDaLrMmtVlQYDuG0iSPYLycG8P9jLI+fRm8AF/xPKhYDB2g6xDWjXBrR5M8gEWsK6KVFlebpZ4QsrxdyIX1Jaw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.6.4", + "@jest/environment": "^29.6.4", + "@jest/test-result": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-docblock": "^29.6.3", + "jest-environment-node": "^29.6.4", + "jest-haste-map": "^29.6.4", + "jest-leak-detector": "^29.6.3", + "jest-message-util": "^29.6.3", + "jest-resolve": "^29.6.4", + "jest-runtime": "^29.6.4", + "jest-util": "^29.6.3", + "jest-watcher": "^29.6.4", + "jest-worker": "^29.6.4", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -7541,8 +7640,9 @@ }, "node_modules/jest-runner/node_modules/source-map-support": { "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -7550,31 +7650,32 @@ } }, "node_modules/jest-runtime": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.4.tgz", + "integrity": "sha512-s/QxMBLvmwLdchKEjcLfwzP7h+jsHvNEtxGP5P+Fl1FMaJX2jMiIqe4rJw4tFprzCwuSvVUo9bn0uj4gNRXsbA==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.6.4", + "@jest/fake-timers": "^29.6.4", + "@jest/globals": "^29.6.4", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-mock": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.6.4", + "jest-snapshot": "^29.6.4", + "jest-util": "^29.6.3", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -7584,50 +7685,50 @@ }, "node_modules/jest-runtime/node_modules/strip-bom": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=8" } }, "node_modules/jest-snapshot": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.4.tgz", + "integrity": "sha512-VC1N8ED7+4uboUKGIDsbvNAZb6LakgIPgAF4RSpF13dN6YaMokfRqO+BaqK4zIh6X3JffgwbzuGqDEjHm/MrvA==", "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", + "@jest/expect-utils": "^29.6.4", + "@jest/transform": "^29.6.4", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.5.0", + "expect": "^29.6.4", "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-diff": "^29.6.4", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.6.4", + "jest-message-util": "^29.6.3", + "jest-util": "^29.6.3", "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" + "pretty-format": "^29.6.3", + "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz", + "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -7639,17 +7740,18 @@ } }, "node_modules/jest-validate": { - "version": "29.5.0", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.3.tgz", + "integrity": "sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.5.0" + "pretty-format": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7657,8 +7759,9 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -7668,18 +7771,19 @@ } }, "node_modules/jest-watcher": { - "version": "29.5.0", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.4.tgz", + "integrity": "sha512-oqUWvx6+On04ShsT00Ir9T4/FvBeEh2M9PTubgITPxDa739p4hoQweWPRGyYeaojgT0xTpZKF0Y/rSY1UgMxvQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/test-result": "^29.6.4", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.5.0", + "jest-util": "^29.6.3", "string-length": "^4.0.1" }, "engines": { @@ -7687,11 +7791,12 @@ } }, "node_modules/jest-worker": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.4", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.4.tgz", + "integrity": "sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q==", "dependencies": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.6.3", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -7701,7 +7806,8 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dependencies": { "has-flag": "^4.0.0" }, @@ -7805,8 +7911,9 @@ }, "node_modules/kleur": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -7814,8 +7921,9 @@ }, "node_modules/leven": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -7889,12 +7997,6 @@ "version": "4.17.21", "license": "MIT" }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/lodash.defaults": { "version": "4.2.0", "license": "MIT" @@ -7987,29 +8089,21 @@ } }, "node_modules/make-dir": { - "version": "3.1.0", + "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", "peer": true, "dependencies": { - "semver": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", "devOptional": true, @@ -8017,7 +8111,8 @@ }, "node_modules/makeerror": { "version": "1.0.12", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dependencies": { "tmpl": "1.0.5" } @@ -8219,7 +8314,8 @@ }, "node_modules/mz": { "version": "2.7.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "peer": true, "dependencies": { "any-promise": "^1.0.0", @@ -8233,8 +8329,9 @@ }, "node_modules/natural-compare-lite": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/negotiator": { @@ -8328,7 +8425,8 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { "version": "2.0.12", @@ -8630,12 +8728,14 @@ }, "node_modules/parse5": { "version": "5.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", "peer": true }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", "peer": true, "dependencies": { "parse5": "^6.0.1" @@ -8643,7 +8743,8 @@ }, "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { "version": "6.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "peer": true }, "node_modules/parseurl": { @@ -8743,8 +8844,9 @@ }, "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", "peer": true, "dependencies": { "find-up": "^4.0.0" @@ -8755,8 +8857,9 @@ }, "node_modules/pkg-dir/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", "peer": true, "dependencies": { "locate-path": "^5.0.0", @@ -8768,8 +8871,9 @@ }, "node_modules/pkg-dir/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", "peer": true, "dependencies": { "p-locate": "^4.1.0" @@ -8780,8 +8884,9 @@ }, "node_modules/pkg-dir/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", "peer": true, "dependencies": { "p-try": "^2.0.0" @@ -8795,8 +8900,9 @@ }, "node_modules/pkg-dir/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", "peer": true, "dependencies": { "p-limit": "^2.2.0" @@ -8822,15 +8928,16 @@ } }, "node_modules/prettier": { - "version": "2.8.8", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz", + "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==", "dev": true, - "license": "MIT", "peer": true, "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" @@ -8848,10 +8955,11 @@ } }, "node_modules/pretty-format": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", + "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -8875,8 +8983,9 @@ }, "node_modules/prompts": { "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "kleur": "^3.0.3", @@ -8926,6 +9035,8 @@ }, "node_modules/pure-rand": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", "dev": true, "funding": [ { @@ -8937,7 +9048,6 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", "peer": true }, "node_modules/qs": { @@ -9075,23 +9185,6 @@ "node": ">= 0.10" } }, - "node_modules/redis": { - "version": "4.6.7", - "license": "MIT", - "optional": true, - "peer": true, - "workspaces": [ - "./packages/*" - ], - "dependencies": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.5.8", - "@redis/graph": "1.1.0", - "@redis/json": "1.0.4", - "@redis/search": "1.1.3", - "@redis/time-series": "1.0.4" - } - }, "node_modules/redis-errors": { "version": "1.2.0", "license": "MIT", @@ -9111,12 +9204,14 @@ }, "node_modules/reflect-metadata": { "version": "0.1.13", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", "peer": true }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "license": "MIT", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", "peer": true }, "node_modules/regexp.prototype.flags": { @@ -9191,8 +9286,9 @@ }, "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", "peer": true, "dependencies": { "resolve-from": "^5.0.0" @@ -9203,8 +9299,9 @@ }, "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", "peer": true, "engines": { "node": ">=8" @@ -9227,8 +9324,9 @@ }, "node_modules/resolve.exports": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, - "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -9492,8 +9590,9 @@ } }, "node_modules/semver": { - "version": "7.5.3", - "license": "ISC", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -9578,7 +9677,8 @@ }, "node_modules/sha.js": { "version": "2.4.11", - "license": "(MIT AND BSD-3-Clause)", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "peer": true, "dependencies": { "inherits": "^2.0.1", @@ -9639,8 +9739,9 @@ }, "node_modules/sisteransi": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/slash": { @@ -9737,8 +9838,9 @@ }, "node_modules/string-length": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { "char-regex": "^1.0.2", @@ -9841,6 +9943,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/superagent": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", + "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "dev": true, + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^2.1.2", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=6.4.0 <13 || >=14" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", + "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "dev": true, + "dependencies": { + "methods": "^1.1.2", + "superagent": "^8.0.5" + }, + "engines": { + "node": ">=6.4.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "license": "MIT", @@ -10018,7 +10166,8 @@ }, "node_modules/thenify": { "version": "3.3.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "peer": true, "dependencies": { "any-promise": "^1.0.0" @@ -10026,7 +10175,8 @@ }, "node_modules/thenify-all": { "version": "1.6.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "peer": true, "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -10068,7 +10218,8 @@ }, "node_modules/tmpl": { "version": "1.0.5", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" }, "node_modules/to-fast-properties": { "version": "2.0.0", @@ -10147,6 +10298,25 @@ } } }, + "node_modules/ts-loader": { + "version": "9.4.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", + "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, "node_modules/ts-node": { "version": "10.9.1", "devOptional": true, @@ -10324,7 +10494,8 @@ }, "node_modules/type-detect": { "version": "4.0.8", - "license": "MIT", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "engines": { "node": ">=4" } @@ -10369,8 +10540,9 @@ "license": "MIT" }, "node_modules/typeorm": { - "version": "0.3.16", - "license": "MIT", + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.17.tgz", + "integrity": "sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==", "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", @@ -10475,7 +10647,8 @@ }, "node_modules/typeorm/node_modules/brace-expansion": { "version": "2.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "peer": true, "dependencies": { "balanced-match": "^1.0.0" @@ -10483,7 +10656,8 @@ }, "node_modules/typeorm/node_modules/glob": { "version": "8.1.0", - "license": "ISC", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "peer": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -10501,7 +10675,8 @@ }, "node_modules/typeorm/node_modules/minimatch": { "version": "5.1.6", - "license": "ISC", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "peer": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -10512,7 +10687,8 @@ }, "node_modules/typeorm/node_modules/mkdirp": { "version": "2.1.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", "peer": true, "bin": { "mkdirp": "dist/cjs/src/bin.js" @@ -10664,8 +10840,9 @@ }, "node_modules/v8-to-istanbul": { "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, - "license": "ISC", "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -10676,35 +10853,22 @@ "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/v8-to-istanbul/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==", "dev": true, - "license": "MIT", "peer": true }, "node_modules/validator": { @@ -10728,7 +10892,8 @@ }, "node_modules/walker": { "version": "1.0.8", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dependencies": { "makeerror": "1.0.12" } diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 38f9869f..4de7f2fc 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -17,7 +17,8 @@ "clean": "rm -Rf dist", "lint": "tsc --noEmit --pretty && eslint \"**/*.ts\" --fix", "pretest": "cp env.template .env", - "test": "jest --coverage --verbose" + "test": "jest --coverage --verbose", + "test:e2e": "jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles" }, "repository": { "type": "git", @@ -36,9 +37,9 @@ "@liaoliaots/nestjs-redis": "^9.0.5", "@nestjs/axios": "^2.0.0", "@nestjs/bullmq": "^10.0.0", + "@nestjs/cli": "^10.1.14", "@nestjs/common": "^9.4.0", "@nestjs/config": "^2.3.1", - "@nestjs/cli": "^10.1.14", "@nestjs/core": "^9.4.0", "@nestjs/event-emitter": "^1.4.1", "@nestjs/platform-express": "^9.4.0", @@ -65,8 +66,11 @@ }, "devDependencies": { "@polkadot/typegen": "10.9.1", + "@types/express": "^4.17.17", "@types/jest": "^29.5.2", + "@types/supertest": "^2.0.12", "@types/time-constants": "^1.0.0", + "@types/node": "^20.3.1", "@typescript-eslint/parser": "^5.59.8", "@typescript-eslint/typescript-estree": "5.59.8", "dotenv": "^16.3.1", @@ -78,13 +82,45 @@ "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.7.0", "eslint-plugin-nestjs": "^1.2.3", - "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", "license-report": "^6.4.0", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", "ts-jest": "^29.1.0", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": ".", + "setupFiles": [ + "dotenv/config" + ], + "testRegex": ".*\\.spec\\.ts$", + "testPathIgnorePatterns": [ + ".*\\.mock\\.spec\\.ts$" + ], + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "./coverage", + "testEnvironment": "node", + "roots": [ + "/apps/", + "/libs/" + ], + "moduleNameMapper": { + "^@content-publishing-common(|/.*)$": "/libs/common/src/$1" + } } } From 98b80b28bc5b4294cb479e057e028aef1f50acf5 Mon Sep 17 00:00:00 2001 From: Aramik Date: Wed, 30 Aug 2023 09:24:15 -0700 Subject: [PATCH 008/137] enqueue requests (#25) --- .../apps/api/src/api.controller.ts | 46 ++++++----------- .../apps/api/src/api.module.ts | 13 ++++- .../apps/api/src/api.service.ts | 36 +++++++++++++ .../apps/api/src/development.controller.ts | 16 ++++-- .../content-watcher/apps/api/src/metadata.ts | 2 +- .../worker/src/publisher/publisher.module.ts | 3 +- .../src/publisher/publishing.service.ts | 5 +- .../libs/common/src/constants.ts | 32 ++---------- .../libs/common/src/dtos/activity.dto.ts | 2 +- .../libs/common/src/dtos/announcement.dto.ts | 10 ++-- .../libs/common/src/dtos/common.dto.ts | 10 ++++ .../libs/common/src/dtos/validation.dto.ts | 32 ++++++++++++ .../content-watcher/libs/common/src/index.ts | 3 ++ .../src/interfaces/request-job.interface.ts | 10 ++++ .../libs/common/src/utils/queues.ts | 51 +++++++++++++++++++ services/content-watcher/package-lock.json | 7 +++ services/content-watcher/package.json | 7 +-- 17 files changed, 206 insertions(+), 79 deletions(-) create mode 100644 services/content-watcher/apps/api/src/api.service.ts create mode 100644 services/content-watcher/libs/common/src/dtos/validation.dto.ts create mode 100644 services/content-watcher/libs/common/src/interfaces/request-job.interface.ts create mode 100644 services/content-watcher/libs/common/src/utils/queues.ts diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index a32d30ac..12144e64 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -3,24 +3,26 @@ import { v4 as uuidv4 } from 'uuid'; import { FilesInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes } from '@nestjs/swagger'; import { + AnnouncementResponseDto, + AnnouncementTypeDto, BroadcastDto, + DSNP_VALID_MIME_TYPES, + DsnpContentHashParam, + DsnpUserIdParam, + FilesUploadDto, + ProfileDto, ReactionDto, ReplyDto, UpdateDto, - ProfileDto, - AnnouncementResponseDto, - FilesUploadDto, UploadResponseDto, - DsnpUserIdParam, - DsnpContentHashParam, } from '../../../libs/common/src'; -import { DSNP_VALID_MIME_TYPES } from '../../../libs/common/src/constants'; +import { ApiService } from './api.service'; @Controller('api') export class ApiController { private readonly logger: Logger; - constructor() { + constructor(private apiService: ApiService) { this.logger = new Logger(this.constructor.name); } @@ -66,54 +68,36 @@ export class ApiController { @Post('content/:userDsnpId/broadcast') @HttpCode(202) async broadcast(@Param() userDsnpId: DsnpUserIdParam, @Body() broadcastDto: BroadcastDto): Promise { - this.logger.log(`broadcast ${userDsnpId}`); - return { - referenceId: uuidv4(), - }; + return this.apiService.enqueueRequest(AnnouncementTypeDto.BROADCAST, userDsnpId.userDsnpId, broadcastDto); } @Post('content/:userDsnpId/reply') @HttpCode(202) async reply(@Param() userDsnpId: DsnpUserIdParam, @Body() replyDto: ReplyDto): Promise { - this.logger.log(`reply ${userDsnpId}`); - return { - referenceId: uuidv4(), - }; + return this.apiService.enqueueRequest(AnnouncementTypeDto.REPLY, userDsnpId.userDsnpId, replyDto); } @Post('content/:userDsnpId/reaction') @HttpCode(202) async reaction(@Param() userDsnpId: DsnpUserIdParam, @Body() reactionDto: ReactionDto): Promise { - this.logger.log(`reaction ${userDsnpId}`); - return { - referenceId: uuidv4(), - }; + return this.apiService.enqueueRequest(AnnouncementTypeDto.REACTION, userDsnpId.userDsnpId, reactionDto); } @Put('content/:userDsnpId/:targetContentHash') @HttpCode(202) async update(@Param() userDsnpId: DsnpUserIdParam, @Param() targetContentHash: DsnpContentHashParam, @Body() updateDto: UpdateDto): Promise { - this.logger.log(`update ${userDsnpId}/${targetContentHash}`); - return { - referenceId: uuidv4(), - }; + return this.apiService.enqueueRequest(AnnouncementTypeDto.UPDATE, userDsnpId.userDsnpId, updateDto, targetContentHash.targetContentHash); } @Delete('content/:userDsnpId/:targetContentHash') @HttpCode(202) async delete(@Param() userDsnpId: DsnpUserIdParam, @Param() targetContentHash: DsnpContentHashParam): Promise { - this.logger.log(`delete ${userDsnpId}/${targetContentHash}`); - return { - referenceId: uuidv4(), - }; + return this.apiService.enqueueRequest(AnnouncementTypeDto.TOMBSTONE, userDsnpId.userDsnpId, undefined, targetContentHash.targetContentHash); } @Put('profile/:userDsnpId') @HttpCode(202) async profile(@Param() userDsnpId: DsnpUserIdParam, @Body() profileDto: ProfileDto): Promise { - this.logger.log(`profile ${userDsnpId}`); - return { - referenceId: uuidv4(), - }; + return this.apiService.enqueueRequest(AnnouncementTypeDto.PROFILE, userDsnpId.userDsnpId, profileDto); } } diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 94100d93..b7f45e39 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -7,10 +7,19 @@ import { ApiController } from './api.controller'; import { ConfigService } from './config/config.service'; import { ConfigModule } from './config/config.module'; import { DevelopmentController } from './development.controller'; +import { QueueConstants } from '../../../libs/common/src'; +import { ApiService } from './api.service'; @Module({ imports: [ - BullModule, + BullModule.forRoot({ + connection: { + enableOfflineQueue: false, + }, + }), + BullModule.registerQueue({ + name: QueueConstants.REQUEST_QUEUE_NAME, + }), ConfigModule, RedisModule.forRootAsync( { @@ -42,7 +51,7 @@ import { DevelopmentController } from './development.controller'; }), ScheduleModule.forRoot(), ], - providers: [ConfigService], + providers: [ConfigService, ApiService], controllers: process.env?.ENABLE_DEV_CONTROLLER === 'true' ? [DevelopmentController, ApiController] : [ApiController], exports: [], }) diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts new file mode 100644 index 00000000..5ca051c4 --- /dev/null +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -0,0 +1,36 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Queue } from 'bullmq'; +import { createHash } from 'crypto'; +import { AnnouncementResponseDto, AnnouncementTypeDto, IRequestJob, QueueConstants, RequestTypeDto } from '../../../libs/common/src'; + +@Injectable() +export class ApiService { + private readonly logger: Logger; + + constructor(@InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue) { + this.logger = new Logger(this.constructor.name); + } + + async enqueueRequest(announcementType: AnnouncementTypeDto, dsnpUserId: string, content?: RequestTypeDto, targetContentHash?: string): Promise { + const data = { + content, + id: '', + announcementType, + dsnpUserId, + targetContentHash, + } as IRequestJob; + data.id = this.calculateJobId(data); + const job = await this.requestQueue.add(`Request Job - ${data.id}`, data, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); // TODO: should come from config + this.logger.debug(job); + return { + referenceId: data.id, + }; + } + + // eslint-disable-next-line class-methods-use-this + calculateJobId(jobWithoutId: IRequestJob): string { + const stringVal = JSON.stringify(jobWithoutId); + return createHash('sha1').update(stringVal).digest('base64url'); + } +} diff --git a/services/content-watcher/apps/api/src/development.controller.ts b/services/content-watcher/apps/api/src/development.controller.ts index 5aed5f47..05ccdaec 100644 --- a/services/content-watcher/apps/api/src/development.controller.ts +++ b/services/content-watcher/apps/api/src/development.controller.ts @@ -4,18 +4,24 @@ To use it, simply rename and remove the '.dev' extension */ // eslint-disable-next-line max-classes-per-file -import { Controller, Logger, Post, Body, Param, Query, HttpException, HttpStatus } from '@nestjs/common'; +import { Controller, Logger, Post, Body, Param, Query, HttpException, HttpStatus, Get } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; -import { plainToClass } from 'class-transformer'; -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import Redis from 'ioredis'; +import { QueueConstants } from '../../../libs/common/src'; @Controller('api/dev') export class DevelopmentController { private readonly logger: Logger; - constructor() { + constructor(@InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue) { this.logger = new Logger(this.constructor.name); } + + @Get('/request/:jobId') + async requestJob(@Param('jobId') jobId: string) { + this.logger.log(jobId); + const job = await this.requestQueue.getJob(jobId); + this.logger.log(job); + return job; + } } diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index 71c2be77..9577d58d 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -5,5 +5,5 @@ export default async () => { ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), ["../../../libs/common/src/dtos/common.dto"]: await import("../../../libs/common/src/dtos/common.dto") }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "UpdateDto": { targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].AnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "DsnpContentHashParam": { targetContentHash: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }]] } }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "UpdateDto": { targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].UpdateAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "DsnpContentHashParam": { targetContentHash: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }], [import("./development.controller"), { "DevelopmentController": { "requestJob": {} } }]] } }; }; \ No newline at end of file diff --git a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts index de3c7fde..583db9a3 100644 --- a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts +++ b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts @@ -11,6 +11,7 @@ import { ConfigModule } from '../../../api/src/config/config.module'; import { ConfigService } from '../../../api/src/config/config.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { IPFSPublisher } from './ipfs.publisher'; +import { QueueConstants } from '../../../../libs/common/src'; @Module({ imports: [ @@ -51,7 +52,7 @@ import { IPFSPublisher } from './ipfs.publisher'; inject: [ConfigService], }), BullModule.registerQueue({ - name: 'publishQueue', + name: QueueConstants.PUBLISH_QUEUE_NAME, defaultJobOptions: { attempts: 1, backoff: { diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts index 88bd6f47..3e58c06b 100644 --- a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts +++ b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts @@ -11,9 +11,10 @@ import { ConfigService } from '../../../api/src/config/config.service'; import { IPublisherJob } from '../interfaces/publisher-job.interface'; import { IPFSPublisher } from './ipfs.publisher'; import { CAPACITY_EPOCH_TIMEOUT_NAME, SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; +import { QueueConstants } from '../../../../libs/common/src'; @Injectable() -@Processor('publishQueue', { +@Processor(QueueConstants.PUBLISH_QUEUE_NAME, { concurrency: 2, }) export class PublishingService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { @@ -23,7 +24,7 @@ export class PublishingService extends WorkerHost implements OnApplicationBootst constructor( @InjectRedis() private cacheManager: Redis, - @InjectQueue('publishQueue') private publishQueue: Queue, + @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, private blockchainService: BlockchainService, private configService: ConfigService, private ipfsPublisher: IPFSPublisher, diff --git a/services/content-watcher/libs/common/src/constants.ts b/services/content-watcher/libs/common/src/constants.ts index 94798661..8ea7d791 100644 --- a/services/content-watcher/libs/common/src/constants.ts +++ b/services/content-watcher/libs/common/src/constants.ts @@ -1,34 +1,8 @@ /** - * Regex for ISO 8601 - * - T separation - * - Required Time - * - Supports fractional seconds - * - Z or hour minute offset + * Number of seconds to create a block in Frequency chain */ -export const ISO8601_REGEX = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,})?(Z|[+-][01][0-9]:[0-5][0-9])?$/; -/** - * DSNP user URI based on DSNP Spec - * example: dsnp://78187493520 - */ -export const DSNP_USER_URI_REGEX = /^dsnp:\/\/[1-9][0-9]{0,19}$/i; -/** - * DSNP content URI based on DSNP Spec - * example: dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef - */ -export const DSNP_CONTENT_URI_REGEX = /^dsnp:\/\/[1-9][0-9]{0,19}\/0x[0-9a-f]+$/i; -/** - * DSNP character ranges for valid emojis - */ -export const DSNP_EMOJI_REGEX = /^[\u{2000}-\u{2BFF}\u{E000}-\u{FFFF}\u{1F000}-\u{FFFFF}]+$/u; -/** - * Activity Stream Duration based on https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration - */ -export const DURATION_REGEX = /^-?P(([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(\.[0-9]+)?S)?)?)+$/; - +export const SECONDS_PER_BLOCK = 12; /** - * Dsnp attachments mime types + * Name of timeout event used for in memory scheduler */ -export const DSNP_VALID_MIME_TYPES = - /(image\/jpeg|image\/png|image\/svg\+xml|image\/webp|image\/gif|video\/mpeg|video\/ogg|video\/webm|video\/H256|video\/mp4|audio\/mpeg|audio\/ogg|audio\/webm)$/; -export const SECONDS_PER_BLOCK = 12; export const CAPACITY_EPOCH_TIMEOUT_NAME = 'capacity-epoch-timeout'; diff --git a/services/content-watcher/libs/common/src/dtos/activity.dto.ts b/services/content-watcher/libs/common/src/dtos/activity.dto.ts index 34cd367f..2c079705 100644 --- a/services/content-watcher/libs/common/src/dtos/activity.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/activity.dto.ts @@ -22,7 +22,7 @@ import { ValidateNested, } from 'class-validator'; import { Type } from 'class-transformer'; -import { DSNP_USER_URI_REGEX, DURATION_REGEX, ISO8601_REGEX } from '../constants'; +import { DSNP_USER_URI_REGEX, DURATION_REGEX, ISO8601_REGEX } from './validation.dto'; // eslint-disable-next-line no-shadow export enum UnitTypeDto { diff --git a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts index 8466d931..857d463f 100644 --- a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts @@ -5,10 +5,10 @@ import { IsEnum, IsInt, IsNotEmpty, IsString, Matches, Max, MaxLength, Min, MinLength, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { NoteActivityDto, ProfileActivityDto } from './activity.dto'; -import { DSNP_CONTENT_URI_REGEX, DSNP_EMOJI_REGEX } from '../constants'; +import { DSNP_CONTENT_URI_REGEX, DSNP_EMOJI_REGEX } from './validation.dto'; // eslint-disable-next-line no-shadow -export enum AnnouncementTypeDto { +export enum UpdateAnnouncementTypeDto { BROADCAST = 'broadcast', REPLY = 'reply', } @@ -32,8 +32,8 @@ export class ReplyDto { } export class UpdateDto { - @IsEnum(AnnouncementTypeDto) - targetAnnouncementType: AnnouncementTypeDto; + @IsEnum(UpdateAnnouncementTypeDto) + targetAnnouncementType: UpdateAnnouncementTypeDto; @IsNotEmpty() @ValidateNested() @@ -63,3 +63,5 @@ export class ProfileDto { @Type(() => ProfileActivityDto) profile: ProfileActivityDto; } + +export type RequestTypeDto = BroadcastDto | ReplyDto | ReactionDto | UpdateDto | ProfileDto; diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index 6b325f1e..68040caf 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -29,3 +29,13 @@ export class FilesUploadDto { @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } }) files: any[]; } + +// eslint-disable-next-line no-shadow +export enum AnnouncementTypeDto { + BROADCAST = 'broadcast', + REPLY = 'reply', + REACTION = 'reaction', + UPDATE = 'update', + TOMBSTONE = 'tombstone', + PROFILE = 'profile', +} diff --git a/services/content-watcher/libs/common/src/dtos/validation.dto.ts b/services/content-watcher/libs/common/src/dtos/validation.dto.ts new file mode 100644 index 00000000..b867b109 --- /dev/null +++ b/services/content-watcher/libs/common/src/dtos/validation.dto.ts @@ -0,0 +1,32 @@ +/** + * Regex for ISO 8601 + * - T separation + * - Required Time + * - Supports fractional seconds + * - Z or hour minute offset + * - example: 1970-01-01T00:00:00+00:00 + */ +export const ISO8601_REGEX = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,})?(Z|[+-][01][0-9]:[0-5][0-9])?$/; +/** + * DSNP user URI based on DSNP Spec + * example: dsnp://78187493520 + */ +export const DSNP_USER_URI_REGEX = /^dsnp:\/\/[1-9][0-9]{0,19}$/i; +/** + * DSNP content URI based on DSNP Spec + * example: dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef + */ +export const DSNP_CONTENT_URI_REGEX = /^dsnp:\/\/[1-9][0-9]{0,19}\/0x[0-9a-f]+$/i; +/** + * DSNP character ranges for valid emojis + */ +export const DSNP_EMOJI_REGEX = /^[\u{2000}-\u{2BFF}\u{E000}-\u{FFFF}\u{1F000}-\u{FFFFF}]+$/u; +/** + * Activity Stream Duration based on https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration + */ +export const DURATION_REGEX = /^-?P(([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([0-9]+M)?([0-9]+(\.[0-9]+)?S)?)?)+$/; +/** + * Dsnp attachments mime types + */ +export const DSNP_VALID_MIME_TYPES = + /(image\/jpeg|image\/png|image\/svg\+xml|image\/webp|image\/gif|video\/mpeg|video\/ogg|video\/webm|video\/H256|video\/mp4|audio\/mpeg|audio\/ogg|audio\/webm)$/; diff --git a/services/content-watcher/libs/common/src/index.ts b/services/content-watcher/libs/common/src/index.ts index 9e674f38..d12f6422 100644 --- a/services/content-watcher/libs/common/src/index.ts +++ b/services/content-watcher/libs/common/src/index.ts @@ -1,3 +1,6 @@ export * from './dtos/announcement.dto'; export * from './dtos/activity.dto'; export * from './dtos/common.dto'; +export * from './dtos/validation.dto'; +export * from './interfaces/request-job.interface'; +export * from './utils/queues'; diff --git a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts new file mode 100644 index 00000000..0798cd4a --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts @@ -0,0 +1,10 @@ +import { RequestTypeDto } from '../dtos/announcement.dto'; +import { AnnouncementTypeDto } from '../dtos/common.dto'; + +export interface IRequestJob { + id: string; + announcementType: AnnouncementTypeDto; + dsnpUserId: string; + targetContentHash?: string; + content?: RequestTypeDto; +} diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts new file mode 100644 index 00000000..e759e7a5 --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -0,0 +1,51 @@ +import { AnnouncementTypeDto } from '../dtos/common.dto'; + +export namespace QueueConstants { + /** + * Name of the queue that has all incoming requests + */ + export const REQUEST_QUEUE_NAME = 'requestQueue'; + /** + * Name of the queue that has all individual announcements batched together + */ + export const BATCH_QUEUE_NAME = 'batchQueue'; + /** + * Name of the queue that has all items that needs to be published to Frequency chain + */ + export const PUBLISH_QUEUE_NAME = 'publishQueue'; + /** + * Name of the queue that has all the jobs and items that needs to run periodically or check their status + */ + export const STATUS_QUEUE_NAME = 'statusQueue'; + /** + * All of the announcement type queues + */ + export const BROADCAST_QUEUE_NAME = 'broadcastQueue'; + export const REPLY_QUEUE_NAME = 'replyQueue'; + export const REACTION_QUEUE_NAME = 'reactionQueue'; + export const UPDATE_QUEUE_NAME = 'updateQueue'; + export const TOMBSTONE_QUEUE_NAME = 'tombstoneQueue'; + export const PROFILE_QUEUE_NAME = 'profileQueue'; + /** + * Map between announcement type and it's queueName + */ + export const ANNOUNCEMENT_TO_QUEUE_NAME_MAP = new Map([ + [AnnouncementTypeDto.BROADCAST, BROADCAST_QUEUE_NAME], + [AnnouncementTypeDto.REPLY, REPLY_QUEUE_NAME], + [AnnouncementTypeDto.REACTION, REACTION_QUEUE_NAME], + [AnnouncementTypeDto.UPDATE, UPDATE_QUEUE_NAME], + [AnnouncementTypeDto.TOMBSTONE, TOMBSTONE_QUEUE_NAME], + [AnnouncementTypeDto.PROFILE, PROFILE_QUEUE_NAME], + ]); + /** + * Map between queue name and it's announcement type + */ + export const QUEUE_NAME_TO_ANNOUNCEMENT_MAP = new Map([ + [BROADCAST_QUEUE_NAME, AnnouncementTypeDto.BROADCAST], + [REPLY_QUEUE_NAME, AnnouncementTypeDto.REPLY], + [REACTION_QUEUE_NAME, AnnouncementTypeDto.REACTION], + [UPDATE_QUEUE_NAME, AnnouncementTypeDto.UPDATE], + [TOMBSTONE_QUEUE_NAME, AnnouncementTypeDto.TOMBSTONE], + [PROFILE_QUEUE_NAME, AnnouncementTypeDto.PROFILE], + ]); +} diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 1bc6bcab..128cb320 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -36,6 +36,7 @@ "bullmq": "^3.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "crypto": "^1.0.1", "ioredis": "^5.3.2", "joi": "^17.9.1", "rxjs": "^7.8.1", @@ -4628,6 +4629,12 @@ "node": ">= 8" } }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "license": "MIT", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 4de7f2fc..c2030f78 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -32,8 +32,8 @@ }, "homepage": "https://github.com/AmplicaLabs/content-publishing-service#readme", "dependencies": { - "@jest/globals": "^29.5.0", "@frequency-chain/api-augment": "1.7.0", + "@jest/globals": "^29.5.0", "@liaoliaots/nestjs-redis": "^9.0.5", "@nestjs/axios": "^2.0.0", "@nestjs/bullmq": "^10.0.0", @@ -59,6 +59,7 @@ "bullmq": "^3.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "crypto": "^1.0.1", "ioredis": "^5.3.2", "joi": "^17.9.1", "rxjs": "^7.8.1", @@ -68,9 +69,9 @@ "@polkadot/typegen": "10.9.1", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@types/time-constants": "^1.0.0", - "@types/node": "^20.3.1", "@typescript-eslint/parser": "^5.59.8", "@typescript-eslint/typescript-estree": "5.59.8", "dotenv": "^16.3.1", @@ -86,8 +87,8 @@ "eslint-plugin-promise": "^6.1.1", "license-report": "^6.4.0", "supertest": "^6.3.3", - "ts-loader": "^9.4.3", "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", From 453d856989bc25c27cb07b805c939334c4a4d22b Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Wed, 30 Aug 2023 13:52:51 -0500 Subject: [PATCH 009/137] add scaffolding for status monitoring (#26) * add scaffodingly for status monitoring * cleanup and move out some complex code * cleanup * cleanup * do not clear publishQueue on complete * error on bad txHash * fix run * register queues with respective modules * cleanup * hash fix --- .../apps/worker/src/blockchain/extrinsic.ts | 59 ++++--------- .../interfaces/status-monitor.interface.ts | 7 ++ .../src/monitor/status.monitor.module.ts | 82 +++++++++++++++++++ .../src/monitor/status.monitor.service.ts | 60 ++++++++++++++ .../src/monitor/status.monitoring.spec.ts | 10 +++ .../worker/src/publisher/ipfs.publisher.ts | 81 ++++++------------ .../worker/src/publisher/publisher.module.ts | 31 +++++-- .../src/publisher/publishing.service.ts | 3 +- .../apps/worker/src/worker.module.ts | 5 +- .../libs/common/src/utils/queues.ts | 5 ++ 10 files changed, 232 insertions(+), 111 deletions(-) create mode 100644 services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts create mode 100644 services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts create mode 100644 services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts create mode 100644 services/content-watcher/apps/worker/src/monitor/status.monitoring.spec.ts diff --git a/services/content-watcher/apps/worker/src/blockchain/extrinsic.ts b/services/content-watcher/apps/worker/src/blockchain/extrinsic.ts index a435e0ab..7472473c 100644 --- a/services/content-watcher/apps/worker/src/blockchain/extrinsic.ts +++ b/services/content-watcher/apps/worker/src/blockchain/extrinsic.ts @@ -26,12 +26,11 @@ import { ApiRx } from '@polkadot/api'; import { SubmittableExtrinsic, ApiTypes, AugmentedEvent } from '@polkadot/api/types'; -import { Call, Event } from '@polkadot/types/interfaces'; +import { Call, Event, EventRecord, Hash } from '@polkadot/types/interfaces'; import { IsEvent } from '@polkadot/types/metadata/decorate/types'; import { Codec, ISubmittableResult, AnyTuple } from '@polkadot/types/types'; import { filter, firstValueFrom, map, pipe, tap } from 'rxjs'; import { KeyringPair } from '@polkadot/keyring/types'; -import { ConfigService } from '../../../api/src/config/config.service'; import { EventError } from './event-error'; export type EventMap = { [key: string]: Event }; @@ -63,12 +62,19 @@ export class Extrinsic { - return firstValueFrom( - this.extrinsic.signAndSend(this.keys, { nonce }).pipe( - filter(({ status }) => status.isInBlock || status.isFinalized), - this.parseResult(this.event), - ), + public signAndSend(nonce?: number): Promise<[Hash, EventMap]> { + return firstValueFrom(this.extrinsic.signAndSend(this.keys, { nonce }).pipe(filter(({ status }) => status.isInBlock || status.isFinalized))).then( + ({ status, events, txHash }) => { + if (status.isInBlock || status.isFinalized) { + const eventMap: EventMap = {}; + events.forEach((record: EventRecord) => { + const { event } = record; + eventMap[eventKey(event)] = event; + }); + return [txHash, eventMap]; + } + throw new Error(`Transaction failed to finalize: ${txHash}`); + }, ); } @@ -76,41 +82,4 @@ export class Extrinsic(targetEvent?: AugmentedEvent) { - return pipe( - tap((result: ISubmittableResult) => { - if (result.dispatchError) { - const err = new EventError(result.dispatchError); - throw err; - } - }), - map((result: ISubmittableResult) => - result.events.reduce((acc, { event }) => { - acc[eventKey(event)] = event; - if (targetEvent && targetEvent.is(event)) { - acc.defaultEvent = event; - } - if (this.api.events.sudo.Sudid.is(event)) { - const { sudoResult } = event.data; - if (sudoResult.isErr) { - const err = new EventError(sudoResult.asErr); - throw err; - } - } - return acc; - }, {} as EventMap), - ), - map((em) => { - const result: ParsedEventResult = [undefined, {}]; - if (targetEvent && targetEvent.is(em?.defaultEvent)) { - result[0] = em.defaultEvent; - } - result[1] = em; - return result; - }), - // tap((events) => console.log(events)), - ); - } } diff --git a/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts b/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts new file mode 100644 index 00000000..e0f10ce0 --- /dev/null +++ b/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts @@ -0,0 +1,7 @@ +import { Hash } from "@polkadot/types/interfaces"; + +export interface IStatusMonitorJob { + id: string; + txHash: Hash; + publisherJobId: string; +} diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts new file mode 100644 index 00000000..70a5452f --- /dev/null +++ b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts @@ -0,0 +1,82 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { BullModule } from '@nestjs/bullmq'; +import { Module } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { StatusMonitoringService } from './status.monitor.service'; +import { ConfigModule } from '../../../api/src/config/config.module'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { BlockchainModule } from '../blockchain/blockchain.module'; +import { QueueConstants } from '../../../../libs/common/src'; + +@Module({ + imports: [ + ConfigModule, + BlockchainModule, + EventEmitterModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue( + { + name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, + defaultJobOptions: { + attempts: 1, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.PUBLISH_QUEUE_NAME, + defaultJobOptions: { + attempts: 1, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + ), + ], + controllers: [], + providers: [StatusMonitoringService], + exports: [BullModule, StatusMonitoringService], +}) +export class StatusMonitorModule {} diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts new file mode 100644 index 00000000..c0347eb5 --- /dev/null +++ b/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts @@ -0,0 +1,60 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; +import Redis from 'ioredis'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { BlockchainService } from '../blockchain/blockchain.service'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { IStatusMonitorJob } from '../interfaces/status-monitor.interface'; +import { QueueConstants } from '../../../../libs/common/src'; + +@Injectable() +@Processor(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, { + concurrency: 2, +}) +export class StatusMonitoringService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { + private logger: Logger; + + constructor( + @InjectRedis() private cacheManager: Redis, + @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, + private blockchainService: BlockchainService, + private configService: ConfigService, + private eventEmitter: EventEmitter2, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + public async onApplicationBootstrap() { + this.logger.debug('Starting publishing service'); + } + + public onModuleDestroy() { + try { + this.logger.debug('Shutting down publishing service'); + } catch (e) { + // 💀 // + } + } + + async process(job: Job): Promise { + this.logger.log(`Processing job ${job.id} of type ${job.name}`); + try { + this.logger.verbose(`Successfully completed job ${job.id}`); + return { success: true }; + } catch (e) { + this.logger.error(`Job ${job.id} failed (attempts=${job.attemptsMade})`); + throw e; + } finally { + // do some stuff + } + } + + // eslint-disable-next-line class-methods-use-this + @OnWorkerEvent('completed') + onCompleted() { + // do some stuff + } +} diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitoring.spec.ts b/services/content-watcher/apps/worker/src/monitor/status.monitoring.spec.ts new file mode 100644 index 00000000..25938c2d --- /dev/null +++ b/services/content-watcher/apps/worker/src/monitor/status.monitoring.spec.ts @@ -0,0 +1,10 @@ +// test file for ipfs publisher +import { describe, it, beforeEach } from '@jest/globals'; + +describe('Status Monitoring', () => { + beforeEach(async () => {}); + + describe('check status', () => { + it('should handle tx receipts', async () => {}); + }); +}); diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts index 064c77fd..c00f46a5 100644 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -3,16 +3,21 @@ import { EventEmitter2 } from '@nestjs/event-emitter'; import { KeyringPair } from '@polkadot/keyring/types'; import { ISubmittableResult } from '@polkadot/types/types'; import { SubmittableExtrinsic } from '@polkadot/api-base/types'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Hash } from '@polkadot/types/interfaces'; import { BlockchainService } from '../blockchain/blockchain.service'; import { ConfigService } from '../../../api/src/config/config.service'; import { IPublisherJob } from '../interfaces/publisher-job.interface'; import { createKeys } from '../blockchain/create-keys'; +import { IStatusMonitorJob } from '../interfaces/status-monitor.interface'; +import { QueueConstants } from '../../../../libs/common/src'; @Injectable() export class IPFSPublisher { private logger: Logger; constructor( + @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue, private configService: ConfigService, private blockchainService: BlockchainService, private eventEmitter: EventEmitter2, @@ -20,69 +25,28 @@ export class IPFSPublisher { this.logger = new Logger(IPFSPublisher.name); } - public async publish(messages: IPublisherJob[]): Promise<{ [key: string]: bigint }> { + public async publish(message: IPublisherJob): Promise<{ [key: string]: bigint }> { const providerKeys = createKeys(this.configService.getProviderAccountSeedPhrase()); - let batch: SubmittableExtrinsic<'rxjs', ISubmittableResult>[] = []; - const batches: SubmittableExtrinsic<'rxjs', ISubmittableResult>[][] = []; - const allowedBatchLen = await this.blockchainService.capacityBatchLimit(); - messages.forEach((message) => { - batch.push(this.blockchainService.createExtrinsicCall({ pallet: 'messages', extrinsic: 'addIpfsMessage' }, message.schemaId, message.data.cid, message.data.payloadLength)); - - if (batch.length === allowedBatchLen) { - batches.push(batch); - batch = []; - } - }); - - if (batch.length > 0) { - batches.push(batch); - } - return this.sendAndProcessChainEvents(providerKeys, batches); + const batch: SubmittableExtrinsic<'rxjs', ISubmittableResult>[] = []; + const tx = this.blockchainService.createExtrinsicCall({ pallet: 'messages', extrinsic: 'addIpfsMessage' }, message.schemaId, message.data.cid, message.data.payloadLength); + return this.processSingleBatch(message.id, providerKeys, tx); } - async sendAndProcessChainEvents(providerKeys: KeyringPair, batchesMap: SubmittableExtrinsic<'rxjs', ISubmittableResult>[][]): Promise<{ [key: string]: bigint }> { - try { - // iterate over batches and send them to the chain returning the capacity withdrawn - const batchPromises: Promise<{ [key: string]: bigint }>[] = []; - - batchesMap.forEach(async (batch) => { - batchPromises.push(this.processSingleBatch(providerKeys, batch)); - }); - - const totalCapUsedPerEpoch = await Promise.all(batchPromises); - const totalCapacityUsed = totalCapUsedPerEpoch.reduce( - (acc, curr) => { - const epoch = Object.keys(curr)[0]; - if (acc[epoch]) { - acc[epoch] += curr[epoch]; - } - acc[epoch] = curr[epoch]; - return acc; - }, - {} as { [key: string]: bigint }, - ); - - this.logger.debug(`Total capacity used: ${JSON.stringify(totalCapacityUsed)}`); - return totalCapacityUsed; - } catch (e) { - this.logger.error(`Error processing batches: ${e}`); - throw e; - } - } - - async processSingleBatch(providerKeys: KeyringPair, batch: SubmittableExtrinsic<'rxjs', ISubmittableResult>[]): Promise<{ [key: string]: bigint }> { - this.logger.debug(`Submitting batch of size ${batch.length}`); + async processSingleBatch(jobId: string, providerKeys: KeyringPair, tx: SubmittableExtrinsic<'rxjs', ISubmittableResult>): Promise<{ [key: string]: bigint }> { + this.logger.debug(`Submitting tx of size ${tx.length}`); try { const currrentEpoch = await this.blockchainService.getCurrentCapacityEpoch(); - const [event, eventMap] = await this.blockchainService - .createExtrinsic({ pallet: 'frequencyTxPayment', extrinsic: 'payWithCapacityBatchAll' }, { eventPallet: 'utility', event: 'BatchCompleted' }, providerKeys, batch) + const [txHash, eventMap] = await this.blockchainService + .createExtrinsic({ pallet: 'frequencyTxPayment', extrinsic: 'payWithCapacity' }, { eventPallet: 'messages', event: 'MessagesStored' }, providerKeys, [tx]) .signAndSend(); - if (!event || !this.blockchainService.api.events.utility.BatchCompleted.is(event)) { - // if we dont get any events, covering any unexpected connection errors - throw new Error(`No events were found for batch`); + if (!txHash) { + throw new Error('Tx hash is undefined'); } + const capacityWithDrawn = BigInt(eventMap['capacity.CapacityWithdrawn'].data[1].toString()); + + this.sendJobToTxReceiptQueue(jobId, txHash); this.logger.debug(`Batch processed, capacity withdrawn: ${capacityWithDrawn}`); return { [currrentEpoch.toString()]: capacityWithDrawn }; } catch (e) { @@ -90,4 +54,13 @@ export class IPFSPublisher { throw e; } } + + async sendJobToTxReceiptQueue(jobId: any, txHash: Hash): Promise { + const job: IStatusMonitorJob = { + id: txHash.toString(), + txHash: txHash, + publisherJobId: jobId, + }; + await this.txReceiptQueue.add(txHash.toString(), job, { removeOnComplete: true, removeOnFail: true }); + } } diff --git a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts index 583db9a3..1a95ca3c 100644 --- a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts +++ b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts @@ -51,17 +51,30 @@ import { QueueConstants } from '../../../../libs/common/src'; }, inject: [ConfigService], }), - BullModule.registerQueue({ - name: QueueConstants.PUBLISH_QUEUE_NAME, - defaultJobOptions: { - attempts: 1, - backoff: { - type: 'exponential', + BullModule.registerQueue( + { + name: QueueConstants.PUBLISH_QUEUE_NAME, + defaultJobOptions: { + attempts: 1, + backoff: { + type: 'exponential', + }, + removeOnComplete: false, + removeOnFail: false, }, - removeOnComplete: true, - removeOnFail: false, }, - }), + { + name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, + defaultJobOptions: { + attempts: 1, + backoff: { + type: 'exponential', + }, + removeOnComplete: false, + removeOnFail: false, + }, + }, + ), ], controllers: [], providers: [PublishingService, IPFSPublisher], diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts index 3e58c06b..b84900d3 100644 --- a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts +++ b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts @@ -50,8 +50,7 @@ export class PublishingService extends WorkerHost implements OnApplicationBootst async process(job: Job): Promise { this.logger.log(`Processing job ${job.id} of type ${job.name}`); try { - // TODO: this is only performing one message per batch, figure out how to batch multiple messages - const totalCapacityUsed = await this.ipfsPublisher.publish([job.data]); + const totalCapacityUsed = await this.ipfsPublisher.publish(job.data); await this.setEpochCapacity(totalCapacityUsed); this.logger.verbose(`Successfully completed job ${job.id}`); diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts index 010cd3c6..745e3d87 100644 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -9,6 +9,8 @@ import { WorkerService } from './worker.service'; import { ConfigService } from '../../api/src/config/config.service'; import { BlockchainModule } from './blockchain/blockchain.module'; import { ConfigModule } from '../../api/src/config/config.module'; +import { StatusMonitorModule } from './monitor/status.monitor.module'; +import { StatusMonitoringService } from './monitor/status.monitor.service'; @Module({ imports: [ @@ -45,7 +47,8 @@ import { ConfigModule } from '../../api/src/config/config.module'; ScheduleModule.forRoot(), PublisherModule, BlockchainModule, + StatusMonitorModule, ], - providers: [ConfigService, WorkerService, PublishingService], + providers: [ConfigService, WorkerService, PublishingService, StatusMonitoringService], }) export class WorkerModule {} diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index e759e7a5..2db48f4a 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -17,6 +17,11 @@ export namespace QueueConstants { * Name of the queue that has all the jobs and items that needs to run periodically or check their status */ export const STATUS_QUEUE_NAME = 'statusQueue'; + + /** + * Name of the queue that has all the transaction receipts + */ + export const TRANSACTION_RECEIPT_QUEUE_NAME = 'transactionReceiptQueue'; /** * All of the announcement type queues */ From 07a917dfa38b02b64591f2e598819974b877d0fd Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Wed, 30 Aug 2023 14:12:11 -0500 Subject: [PATCH 010/137] scaffolding for batch announcer (#19) * scaffolding for batch announcer * import parquet js from dsnp * lint * address feedback * lint * cleanup --- .../batch_announcer/batch.announcer.module.ts | 81 + .../batch.announcer.service.ts | 64 + .../batch_announcer/ipfs.announcer.spec.ts | 13 + .../src/batch_announcer/ipfs.announcer.ts | 22 + .../batch-announcer.job.interface.ts | 4 + .../interfaces/status-monitor.interface.ts | 2 +- .../worker/src/publisher/ipfs.publisher.ts | 2 +- .../apps/worker/src/worker.module.ts | 5 +- services/content-watcher/package-lock.json | 1441 ++++++++++------- services/content-watcher/package.json | 1 + 10 files changed, 1011 insertions(+), 624 deletions(-) create mode 100644 services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts create mode 100644 services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts create mode 100644 services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.spec.ts create mode 100644 services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts create mode 100644 services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts new file mode 100644 index 00000000..206e1284 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts @@ -0,0 +1,81 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { BullModule } from '@nestjs/bullmq'; +import { Module } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { BatchAnnouncementService } from './batch.announcer.service'; +import { ConfigModule } from '../../../api/src/config/config.module'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { IPFSAnnouncer } from './ipfs.announcer'; +import { QueueConstants } from '../../../../libs/common/src'; + +@Module({ + imports: [ + ConfigModule, + EventEmitterModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue( + { + name: QueueConstants.PUBLISH_QUEUE_NAME, + defaultJobOptions: { + attempts: 1, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.BATCH_QUEUE_NAME, + defaultJobOptions: { + attempts: 1, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + ), + ], + controllers: [], + providers: [BatchAnnouncementService, IPFSAnnouncer], + exports: [BullModule, BatchAnnouncementService, IPFSAnnouncer], +}) +export class BatchAnnouncerModule {} diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts new file mode 100644 index 00000000..9a9ce51b --- /dev/null +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts @@ -0,0 +1,64 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; +import Redis from 'ioredis'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { IPFSAnnouncer } from './ipfs.announcer'; +import { CAPACITY_EPOCH_TIMEOUT_NAME } from '../../../../libs/common/src/constants'; +import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; +import { QueueConstants } from '../../../../libs/common/src'; + +@Injectable() +@Processor(QueueConstants.BATCH_QUEUE_NAME, { + concurrency: 2, +}) +export class BatchAnnouncementService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { + private logger: Logger; + + private capacityExhausted = false; + + constructor( + @InjectRedis() private cacheManager: Redis, + @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, + private configService: ConfigService, + private ipfsPublisher: IPFSAnnouncer, + private schedulerRegistry: SchedulerRegistry, + private eventEmitter: EventEmitter2, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + public async onApplicationBootstrap() { + this.logger.log('onApplicationBootstrap'); + } + + public onModuleDestroy() { + try { + this.schedulerRegistry.deleteTimeout(CAPACITY_EPOCH_TIMEOUT_NAME); + } catch (e) { + // 💀 // + } + } + + async process(job: Job): Promise { + this.logger.log(`Processing job ${job.id} of type ${job.name}`); + try { + await this.ipfsPublisher.announce(job.data); + this.logger.log(`Completed job ${job.id} of type ${job.name}`); + return job.data; + } catch (e) { + this.logger.error(`Error processing job ${job.id} of type ${job.name}: ${e}`); + throw e; + } + } + + // eslint-disable-next-line class-methods-use-this + @OnWorkerEvent('completed') + onCompleted() { + // do some stuff + } +} diff --git a/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.spec.ts b/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.spec.ts new file mode 100644 index 00000000..fafa3c61 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.spec.ts @@ -0,0 +1,13 @@ +// test file for ipfs announcer +import { describe, it, beforeEach } from '@jest/globals'; +import { IPFSAnnouncer } from './ipfs.announcer'; + +describe('IPFSAnnouncer', () => { + let ipfsAnnouncer: IPFSAnnouncer; + + beforeEach(async () => {}); + + describe('announce', () => { + it('should announce a batch on ipfs', async () => {}); + }); +}); diff --git a/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts new file mode 100644 index 00000000..bc94d958 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts @@ -0,0 +1,22 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { BlockchainService } from '../blockchain/blockchain.service'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; + +@Injectable() +export class IPFSAnnouncer { + private logger: Logger; + + constructor( + private configService: ConfigService, + private blockchainService: BlockchainService, + private eventEmitter: EventEmitter2, + ) { + this.logger = new Logger(IPFSAnnouncer.name); + } + + public async announce(batchJob: IBatchAnnouncerJobData): Promise { + this.logger.log(`Announcing batch ${batchJob.batchId} on IPFS`); + } +} diff --git a/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts b/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts new file mode 100644 index 00000000..25d5bbf9 --- /dev/null +++ b/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts @@ -0,0 +1,4 @@ +export interface IBatchAnnouncerJobData { + batchId: string; + schemaId: number; +} diff --git a/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts b/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts index e0f10ce0..c896f558 100644 --- a/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts +++ b/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts @@ -1,4 +1,4 @@ -import { Hash } from "@polkadot/types/interfaces"; +import { Hash } from '@polkadot/types/interfaces'; export interface IStatusMonitorJob { id: string; diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts index c00f46a5..31279c76 100644 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -58,7 +58,7 @@ export class IPFSPublisher { async sendJobToTxReceiptQueue(jobId: any, txHash: Hash): Promise { const job: IStatusMonitorJob = { id: txHash.toString(), - txHash: txHash, + txHash, publisherJobId: jobId, }; await this.txReceiptQueue.add(txHash.toString(), job, { removeOnComplete: true, removeOnFail: true }); diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts index 745e3d87..ea631a37 100644 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -9,6 +9,8 @@ import { WorkerService } from './worker.service'; import { ConfigService } from '../../api/src/config/config.service'; import { BlockchainModule } from './blockchain/blockchain.module'; import { ConfigModule } from '../../api/src/config/config.module'; +import { BatchAnnouncementService } from './batch_announcer/batch.announcer.service'; +import { BatchAnnouncerModule } from './batch_announcer/batch.announcer.module'; import { StatusMonitorModule } from './monitor/status.monitor.module'; import { StatusMonitoringService } from './monitor/status.monitor.service'; @@ -47,8 +49,9 @@ import { StatusMonitoringService } from './monitor/status.monitor.service'; ScheduleModule.forRoot(), PublisherModule, BlockchainModule, + BatchAnnouncerModule, StatusMonitorModule, ], - providers: [ConfigService, WorkerService, PublishingService, StatusMonitoringService], + providers: [BatchAnnouncementService, ConfigService, WorkerService, PublishingService, StatusMonitoringService], }) export class WorkerModule {} diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 128cb320..d6f3ede8 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { + "@dsnp/parquetjs": "^1.3.4", "@frequency-chain/api-augment": "1.7.0", "@jest/globals": "^29.5.0", "@liaoliaots/nestjs-redis": "^9.0.5", @@ -674,12 +675,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.11.tgz", - "integrity": "sha512-ee7jVNlWN09+KftVOu9n7S8gQzD/Z6hN/I8VBRXW4P1+Xe7kJGXMwu8vds4aGIMHZnNbdpSWCfZZtinytpcAvA==", + "version": "7.22.3", + "license": "MIT", "peer": true, "dependencies": { - "regenerator-runtime": "^0.14.0" + "regenerator-runtime": "^0.13.11" }, "engines": { "node": ">=6.9.0" @@ -737,9 +737,8 @@ }, "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", "peer": true }, "node_modules/@colors/colors": { @@ -762,6 +761,29 @@ "node": ">=12" } }, + "node_modules/@dsnp/parquetjs": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@dsnp/parquetjs/-/parquetjs-1.3.4.tgz", + "integrity": "sha512-DKRtMi+EZaoF/IUalz0UhSqRJ4P2/SCcL1aTwCJ5ZAgZGKFHKvOUYcCjM2XrMlsykfKcL3IMlJ4EZZONCGmRIQ==", + "dependencies": { + "@types/long": "^4.0.2", + "@types/node-int64": "^0.4.29", + "@types/thrift": "^0.10.11", + "browserify-zlib": "^0.2.0", + "bson": "4.6.3", + "cross-fetch": "^3.1.4", + "int53": "^0.2.4", + "long": "^4.0.0", + "snappyjs": "^0.6.1", + "thrift": "0.16.0", + "varint": "^6.0.0", + "wasm-brotli": "^2.0.2", + "xxhash-wasm": "^1.0.2" + }, + "engines": { + "node": ">=16.15.1" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, @@ -959,17 +981,16 @@ } }, "node_modules/@jest/console": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.4.tgz", - "integrity": "sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "^29.5.0", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", "slash": "^3.0.0" }, "engines": { @@ -977,38 +998,37 @@ } }, "node_modules/@jest/core": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.4.tgz", - "integrity": "sha512-U/vq5ccNTSVgYH7mHnodHmCffGWHJnz/E1BEWlLuK5pM4FZmGfBn/nrJGLjUsSmyx3otCeqc1T31F4y08AMDLg==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/console": "^29.6.4", - "@jest/reporters": "^29.6.4", - "@jest/test-result": "^29.6.4", - "@jest/transform": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.6.3", - "jest-config": "^29.6.4", - "jest-haste-map": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.4", - "jest-resolve-dependencies": "^29.6.4", - "jest-runner": "^29.6.4", - "jest-runtime": "^29.6.4", - "jest-snapshot": "^29.6.4", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", - "jest-watcher": "^29.6.4", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", "micromatch": "^4.0.4", - "pretty-format": "^29.6.3", + "pretty-format": "^29.5.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -1025,85 +1045,79 @@ } }, "node_modules/@jest/environment": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.4.tgz", - "integrity": "sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", "@types/node": "*", - "jest-mock": "^29.6.3" + "jest-mock": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.4.tgz", - "integrity": "sha512-Warhsa7d23+3X5bLbrbYvaehcgX5TLYhI03JKoedTiI8uJU4IhqYBWF7OSSgUyz4IgLpUYPkK0AehA5/fRclAA==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "expect": "^29.6.4", - "jest-snapshot": "^29.6.4" + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.4.tgz", - "integrity": "sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "jest-get-type": "^29.4.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.4.tgz", - "integrity": "sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "^29.5.0", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.6.3", - "jest-mock": "^29.6.3", - "jest-util": "^29.6.3" + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.4.tgz", - "integrity": "sha512-wVIn5bdtjlChhXAzVXavcY/3PEjf4VqM174BM3eGL5kMxLiZD5CLnbmkEyA1Dwh9q8XjP6E8RwjBsY/iCWrWsA==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/environment": "^29.6.4", - "@jest/expect": "^29.6.4", - "@jest/types": "^29.6.3", - "jest-mock": "^29.6.3" + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.4.tgz", - "integrity": "sha512-sxUjWxm7QdchdrD3NfWKrL8FBsortZeibSJv4XLjESOOjSUOkjQcb0ZHJwfhEGIvBvTluTzfG2yZWZhkrXJu8g==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.6.4", - "@jest/test-result": "^29.6.4", - "@jest/transform": "^29.6.4", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -1111,13 +1125,13 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-instrument": "^5.1.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", - "jest-worker": "^29.6.4", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1135,53 +1149,48 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "node_modules/@jest/reporters/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", "dev": true, + "license": "MIT", "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", - "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", + "node_modules/@jest/reporters/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "version": "29.4.3", + "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@sinclair/typebox": "^0.25.16" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "version": "29.4.3", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", + "@jridgewell/trace-mapping": "^0.3.15", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -1189,26 +1198,39 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/source-map/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.18", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "node_modules/@jest/test-result": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.4.tgz", - "integrity": "sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/console": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1217,15 +1239,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.4.tgz", - "integrity": "sha512-E84M6LbpcRq3fT4ckfKs9ryVanwkaIB0Ws9bw3/yP4seRLg/VaCZ/LgW0MCq5wwk4/iP/qnilD41aj2fsw2RMg==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/test-result": "^29.6.4", + "@jest/test-result": "^29.5.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.4", + "jest-haste-map": "^29.5.0", "slash": "^3.0.0" }, "engines": { @@ -1233,21 +1254,20 @@ } }, "node_modules/@jest/transform": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.4.tgz", - "integrity": "sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA==", + "version": "29.5.0", + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.4", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1277,11 +1297,10 @@ } }, "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", + "@jest/schemas": "^29.4.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1791,8 +1810,7 @@ }, "node_modules/@nestjs/testing": { "version": "9.4.3", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.4.3.tgz", - "integrity": "sha512-LDT8Ai2eKnTzvnPaJwWOK03qTaFap5uHHsJCv6dL0uKWk6hyF9jms8DjyVaGsaujCaXDG8izl1mDEER0OmxaZA==", + "license": "MIT", "dependencies": { "tslib": "2.5.3" }, @@ -2443,6 +2461,65 @@ "node": ">=16" } }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.5.8", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.0", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.4", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.1.3", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.4", + "license": "MIT", + "optional": true, + "peer": true, + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@scure/base": { "version": "1.1.1", "funding": [ @@ -2469,9 +2546,8 @@ "license": "BSD-3-Clause" }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + "version": "0.25.24", + "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "5.4.1", @@ -2486,24 +2562,21 @@ }, "node_modules/@sinonjs/commons": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "10.1.0", + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "node_modules/@sqltools/formatter": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", - "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", + "license": "MIT", "peer": true }, "node_modules/@substrate/connect": { @@ -2563,9 +2636,8 @@ }, "node_modules/@types/babel__core": { "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", - "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@babel/parser": "^7.20.7", @@ -2577,9 +2649,8 @@ }, "node_modules/@types/babel__generator": { "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@babel/types": "^7.0.0" @@ -2587,9 +2658,8 @@ }, "node_modules/@types/babel__template": { "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@babel/parser": "^7.1.0", @@ -2598,10 +2668,7 @@ }, "node_modules/@types/babel__traverse": { "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", - "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", - "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" } @@ -2683,8 +2750,7 @@ }, "node_modules/@types/graceful-fs": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -2735,6 +2801,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -2753,11 +2824,28 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" }, + "node_modules/@types/node-int64": { + "version": "0.4.29", + "resolved": "https://registry.npmjs.org/@types/node-int64/-/node-int64-0.4.29.tgz", + "integrity": "sha512-rHXvenLTj/CcsmNAebaBOhxQ2MqEGl3yXZZcZ21XYR+gzGTTcpOy2N4IxpvTCz48loyQNatHvfn6GhIbbZ1R3Q==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "license": "MIT" + }, + "node_modules/@types/q": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", + "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -2769,10 +2857,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "version": "7.5.0", "dev": true, + "license": "MIT", "peer": true }, "node_modules/@types/send": { @@ -2827,6 +2914,16 @@ "@types/superagent": "*" } }, + "node_modules/@types/thrift": { + "version": "0.10.13", + "resolved": "https://registry.npmjs.org/@types/thrift/-/thrift-0.10.13.tgz", + "integrity": "sha512-zNapgGgZP2tOC8zhS10LPKdJxH+U0owZ1WWwDZASgyb5HZGj03P4Wm+Yd3YDXDQEjSRqO2XQznUH13tcG4dkIA==", + "dependencies": { + "@types/node": "*", + "@types/node-int64": "*", + "@types/q": "*" + } + }, "node_modules/@types/time-constants": { "version": "1.0.0", "dev": true, @@ -2853,18 +2950,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", - "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/type-utils": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/type-utils": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", - "graphemer": "^1.4.0", + "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", "semver": "^7.3.7", @@ -2888,14 +2984,13 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2906,10 +3001,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2920,13 +3014,12 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3006,14 +3099,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", - "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.62.0", - "@typescript-eslint/utils": "5.62.0", + "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/utils": "5.59.11", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3034,10 +3126,9 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3048,14 +3139,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "5.59.11", "dev": true, + "license": "BSD-2-Clause", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3076,13 +3166,12 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3160,18 +3249,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/scope-manager": "5.59.11", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.11", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -3187,14 +3275,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3205,10 +3292,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3219,14 +3305,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "version": "5.59.11", "dev": true, + "license": "BSD-2-Clause", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", + "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/visitor-keys": "5.59.11", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3247,13 +3332,12 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "version": "5.59.11", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/types": "5.59.11", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3577,8 +3661,7 @@ }, "node_modules/any-promise": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT", "peer": true }, "node_modules/anymatch": { @@ -3594,8 +3677,7 @@ }, "node_modules/app-root-path": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "license": "MIT", "peer": true, "engines": { "node": ">= 6.0.0" @@ -3701,6 +3783,17 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "node_modules/async": { + "version": "3.2.3", + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, "node_modules/asynckit": { "version": "0.4.0", "license": "MIT" @@ -3726,16 +3819,15 @@ } }, "node_modules/babel-jest": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.4.tgz", - "integrity": "sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/transform": "^29.6.4", + "@jest/transform": "^29.5.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", + "babel-preset-jest": "^29.5.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" @@ -3762,10 +3854,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@babel/template": "^7.3.3", @@ -3799,13 +3890,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", + "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" }, "engines": { @@ -3964,6 +4054,24 @@ "node": ">=8" } }, + "node_modules/browser-or-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz", + "integrity": "sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg==" + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserify-zlib/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/browserslist": { "version": "4.21.8", "funding": [ @@ -4007,16 +4115,47 @@ }, "node_modules/bser": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.3.tgz", + "integrity": "sha512-rAqP5hcUVJhXP2MCSNVsf0oM2OGU1So6A9pVRDYayvJ5+hygXHQApf87wd5NlhPM1J9RJnbqxIG/f8QTzRoQ4A==", + "dependencies": { + "buffer": "^5.6.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/bson/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "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", @@ -4031,6 +4170,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "peer": true, "dependencies": { "base64-js": "^1.3.1", @@ -4127,6 +4267,26 @@ "node": ">= 0.8" } }, + "node_modules/cache-manager": { + "version": "4.1.0", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "async": "3.2.3", + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^7.10.1" + } + }, + "node_modules/cache-manager/node_modules/lru-cache": { + "version": "7.18.3", + "license": "ISC", + "optional": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/cacheable-lookup": { "version": "7.0.0", "dev": true, @@ -4211,9 +4371,8 @@ }, "node_modules/char-regex": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -4282,9 +4441,8 @@ }, "node_modules/cjs-module-lexer": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/class-transformer": { @@ -4313,8 +4471,7 @@ }, "node_modules/cli-highlight": { "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "license": "ISC", "peer": true, "dependencies": { "chalk": "^4.0.0", @@ -4334,8 +4491,7 @@ }, "node_modules/cli-highlight/node_modules/cliui": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", "peer": true, "dependencies": { "string-width": "^4.2.0", @@ -4345,8 +4501,7 @@ }, "node_modules/cli-highlight/node_modules/yargs": { "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", "peer": true, "dependencies": { "cliui": "^7.0.2", @@ -4363,8 +4518,7 @@ }, "node_modules/cli-highlight/node_modules/yargs-parser": { "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", "peer": true, "engines": { "node": ">=10" @@ -4432,9 +4586,8 @@ }, "node_modules/co": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "iojs": ">= 1.0.0", @@ -4442,10 +4595,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "version": "1.0.1", "dev": true, + "license": "MIT", "peer": true }, "node_modules/color-convert": { @@ -4617,6 +4769,33 @@ "node": ">=12.0.0" } }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "license": "MIT", @@ -4644,8 +4823,7 @@ }, "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==", + "license": "MIT", "peer": true, "dependencies": { "@babel/runtime": "^7.21.0" @@ -4699,19 +4877,10 @@ } }, "node_modules/dedent": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", - "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "version": "0.7.0", "dev": true, - "peer": true, - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } + "license": "MIT", + "peer": true }, "node_modules/deep-extend": { "version": "0.6.0", @@ -4841,9 +5010,8 @@ }, "node_modules/detect-newline": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8" @@ -4868,9 +5036,8 @@ } }, "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "version": "29.4.3", + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -4932,9 +5099,8 @@ }, "node_modules/emittery": { "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=12" @@ -5691,8 +5857,6 @@ }, "node_modules/exit": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "peer": true, "engines": { @@ -5700,15 +5864,14 @@ } }, "node_modules/expect": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.4.tgz", - "integrity": "sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.6.4", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3" + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -5875,8 +6038,7 @@ }, "node_modules/fb-watchman": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" } @@ -6138,19 +6300,6 @@ "version": "1.0.0", "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==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "license": "MIT" @@ -6180,6 +6329,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generic-pool": { + "version": "3.9.0", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "license": "MIT", @@ -6379,6 +6537,12 @@ "version": "4.2.11", "license": "ISC" }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/graphemer": { "version": "1.4.0", "dev": true, @@ -6493,8 +6657,7 @@ }, "node_modules/highlight.js": { "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", "peer": true, "engines": { "node": "*" @@ -6502,9 +6665,8 @@ }, "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", "peer": true }, "node_modules/http-cache-semantics": { @@ -6598,9 +6760,8 @@ }, "node_modules/import-local": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "pkg-dir": "^4.2.0", @@ -6678,6 +6839,11 @@ "node": ">=8" } }, + "node_modules/int53": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/int53/-/int53-0.2.4.tgz", + "integrity": "sha512-a5jlKftS7HUOhkUyYD7j2sJ/ZnvWiNlZS1ldR+g1ifQ+/UuZXIE+YTc/lK1qGj/GwAU5F8Z0e1eVq2t1J5Ob2g==" + }, "node_modules/internal-slot": { "version": "1.0.5", "dev": true, @@ -6846,9 +7012,8 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -7067,6 +7232,14 @@ "version": "2.0.0", "license": "ISC" }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "license": "BSD-3-Clause", @@ -7096,25 +7269,23 @@ } }, "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==", + "version": "3.0.0", "dev": true, + "license": "BSD-3-Clause", "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", + "make-dir": "^3.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=8" } }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, + "license": "BSD-3-Clause", "peer": true, "dependencies": { "debug": "^4.1.1", @@ -7126,10 +7297,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.5", "dev": true, + "license": "BSD-3-Clause", "peer": true, "dependencies": { "html-escaper": "^2.0.0", @@ -7147,16 +7317,15 @@ } }, "node_modules/jest": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.4.tgz", - "integrity": "sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/core": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", "import-local": "^3.0.2", - "jest-cli": "^29.6.4" + "jest-cli": "^29.5.0" }, "bin": { "jest": "bin/jest.js" @@ -7174,14 +7343,12 @@ } }, "node_modules/jest-changed-files": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.6.3.tgz", - "integrity": "sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "execa": "^5.0.0", - "jest-util": "^29.6.3", "p-limit": "^3.1.0" }, "engines": { @@ -7190,9 +7357,8 @@ }, "node_modules/jest-changed-files/node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "cross-spawn": "^7.0.3", @@ -7214,9 +7380,8 @@ }, "node_modules/jest-changed-files/node_modules/human-signals": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "peer": true, "engines": { "node": ">=10.17.0" @@ -7224,9 +7389,8 @@ }, "node_modules/jest-changed-files/node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8" @@ -7237,9 +7401,8 @@ }, "node_modules/jest-changed-files/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", "peer": true, "engines": { "node": ">=6" @@ -7247,9 +7410,8 @@ }, "node_modules/jest-changed-files/node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "path-key": "^3.0.0" @@ -7260,9 +7422,8 @@ }, "node_modules/jest-changed-files/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", "peer": true, "dependencies": { "mimic-fn": "^2.1.0" @@ -7276,38 +7437,36 @@ }, "node_modules/jest-changed-files/node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" } }, "node_modules/jest-circus": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.4.tgz", - "integrity": "sha512-YXNrRyntVUgDfZbjXWBMPslX1mQ8MrSG0oM/Y06j9EYubODIyHWP8hMUbjbZ19M3M+zamqEur7O80HODwACoJw==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/environment": "^29.6.4", - "@jest/expect": "^29.6.4", - "@jest/test-result": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^1.0.0", + "dedent": "^0.7.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.6.3", - "jest-matcher-utils": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-runtime": "^29.6.4", - "jest-snapshot": "^29.6.4", - "jest-util": "^29.6.3", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", "p-limit": "^3.1.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.5.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -7317,22 +7476,21 @@ } }, "node_modules/jest-cli": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.4.tgz", - "integrity": "sha512-+uMCQ7oizMmh8ZwRfZzKIEszFY9ksjjEQnTEMTaL7fYiL3Kw4XhqT9bYh+A4DQKUb67hZn2KbtEnDuHvcgK4pQ==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/core": "^29.6.4", - "@jest/test-result": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.6.4", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", "prompts": "^2.0.1", "yargs": "^17.3.1" }, @@ -7352,32 +7510,31 @@ } }, "node_modules/jest-config": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.4.tgz", - "integrity": "sha512-JWohr3i9m2cVpBumQFv2akMEnFEPVOh+9L2xIBJhJ0zOaci2ZXuKJj0tgMKQCBZAKA09H049IR4HVS/43Qb19A==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.6.4", - "@jest/types": "^29.6.3", - "babel-jest": "^29.6.4", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.6.4", - "jest-environment-node": "^29.6.4", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.4", - "jest-runner": "^29.6.4", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.5.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -7398,24 +7555,22 @@ } }, "node_modules/jest-diff": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.4.tgz", - "integrity": "sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==", + "version": "29.5.0", + "license": "MIT", "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.6.3.tgz", - "integrity": "sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ==", + "version": "29.4.3", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "detect-newline": "^3.0.0" @@ -7425,62 +7580,58 @@ } }, "node_modules/jest-each": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.3.tgz", - "integrity": "sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "^29.5.0", "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.6.3", - "pretty-format": "^29.6.3" + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.4.tgz", - "integrity": "sha512-i7SbpH2dEIFGNmxGCpSc2w9cA4qVD+wfvg2ZnfQ7XVrKL0NA5uDVBIiGH8SR4F0dKEv/0qI5r+aDomDf04DpEQ==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/environment": "^29.6.4", - "@jest/fake-timers": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", "@types/node": "*", - "jest-mock": "^29.6.3", - "jest-util": "^29.6.3" + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "version": "29.4.3", + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.4.tgz", - "integrity": "sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "^29.5.0", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.6.3", - "jest-worker": "^29.6.4", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -7492,45 +7643,42 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz", - "integrity": "sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz", - "integrity": "sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==", + "version": "29.5.0", + "license": "MIT", "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.6.4", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz", - "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==", + "version": "29.5.0", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", + "@jest/types": "^29.5.0", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.6.3", + "pretty-format": "^29.5.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -7539,13 +7687,12 @@ } }, "node_modules/jest-mock": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.3.tgz", - "integrity": "sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "^29.5.0", "@types/node": "*", - "jest-util": "^29.6.3" + "jest-util": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7553,9 +7700,8 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -7570,26 +7716,24 @@ } }, "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "version": "29.4.3", + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.4.tgz", - "integrity": "sha512-fPRq+0vcxsuGlG0O3gyoqGTAxasagOxEuyoxHeyxaZbc9QNek0AmJWSkhjlMG+mTsj+8knc/mWb3fXlRNVih7Q==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.4", + "jest-haste-map": "^29.5.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -7599,45 +7743,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.4.tgz", - "integrity": "sha512-7+6eAmr1ZBF3vOAJVsfLj1QdqeXG+WYhidfLHBRZqGN24MFRIiKG20ItpLw2qRAsW/D2ZUUmCNf6irUr/v6KHA==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.6.4" + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.4.tgz", - "integrity": "sha512-SDaLrMmtVlQYDuG0iSPYLycG8P9jLI+fRm8AF/xPKhYDB2g6xDWjXBrR5M8gEWsK6KVFlebpZ4QsrxdyIX1Jaw==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/console": "^29.6.4", - "@jest/environment": "^29.6.4", - "@jest/test-result": "^29.6.4", - "@jest/transform": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.6.3", - "jest-environment-node": "^29.6.4", - "jest-haste-map": "^29.6.4", - "jest-leak-detector": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-resolve": "^29.6.4", - "jest-runtime": "^29.6.4", - "jest-util": "^29.6.3", - "jest-watcher": "^29.6.4", - "jest-worker": "^29.6.4", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -7647,9 +7789,8 @@ }, "node_modules/jest-runner/node_modules/source-map-support": { "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -7657,32 +7798,31 @@ } }, "node_modules/jest-runtime": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.4.tgz", - "integrity": "sha512-s/QxMBLvmwLdchKEjcLfwzP7h+jsHvNEtxGP5P+Fl1FMaJX2jMiIqe4rJw4tFprzCwuSvVUo9bn0uj4gNRXsbA==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/environment": "^29.6.4", - "@jest/fake-timers": "^29.6.4", - "@jest/globals": "^29.6.4", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.6.4", - "@jest/transform": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-mock": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.4", - "jest-snapshot": "^29.6.4", - "jest-util": "^29.6.3", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -7692,50 +7832,50 @@ }, "node_modules/jest-runtime/node_modules/strip-bom": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8" } }, "node_modules/jest-snapshot": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.4.tgz", - "integrity": "sha512-VC1N8ED7+4uboUKGIDsbvNAZb6LakgIPgAF4RSpF13dN6YaMokfRqO+BaqK4zIh6X3JffgwbzuGqDEjHm/MrvA==", + "version": "29.5.0", + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.6.4", - "@jest/transform": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.6.4", + "expect": "^29.5.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.6.4", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.6.4", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.6.3", - "semver": "^7.5.3" + "pretty-format": "^29.5.0", + "semver": "^7.3.5" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz", - "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "^29.5.0", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -7747,18 +7887,17 @@ } }, "node_modules/jest-validate": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.3.tgz", - "integrity": "sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "^29.5.0", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", + "jest-get-type": "^29.4.3", "leven": "^3.1.0", - "pretty-format": "^29.6.3" + "pretty-format": "^29.5.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -7766,9 +7905,8 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -7778,19 +7916,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.4.tgz", - "integrity": "sha512-oqUWvx6+On04ShsT00Ir9T4/FvBeEh2M9PTubgITPxDa739p4hoQweWPRGyYeaojgT0xTpZKF0Y/rSY1UgMxvQ==", + "version": "29.5.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jest/test-result": "^29.6.4", - "@jest/types": "^29.6.3", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.6.3", + "jest-util": "^29.5.0", "string-length": "^4.0.1" }, "engines": { @@ -7798,12 +7935,11 @@ } }, "node_modules/jest-worker": { - "version": "29.6.4", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.4.tgz", - "integrity": "sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q==", + "version": "29.5.0", + "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.6.3", + "jest-util": "^29.5.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -7813,8 +7949,7 @@ }, "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==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -7918,9 +8053,8 @@ }, "node_modules/kleur": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -7928,9 +8062,8 @@ }, "node_modules/leven": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -8004,6 +8137,12 @@ "version": "4.17.21", "license": "MIT" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/lodash.defaults": { "version": "4.2.0", "license": "MIT" @@ -8037,6 +8176,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, "node_modules/lower-case": { "version": "2.0.2", "dev": true, @@ -8096,21 +8240,29 @@ } }, "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==", + "version": "3.1.0", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "semver": "^7.5.3" + "semver": "^6.0.0" }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "devOptional": true, @@ -8118,8 +8270,7 @@ }, "node_modules/makeerror": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" } @@ -8321,8 +8472,7 @@ }, "node_modules/mz": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", "peer": true, "dependencies": { "any-promise": "^1.0.0", @@ -8336,9 +8486,8 @@ }, "node_modules/natural-compare-lite": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/negotiator": { @@ -8432,8 +8581,7 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.12", @@ -8735,14 +8883,12 @@ }, "node_modules/parse5": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "license": "MIT", "peer": true }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "license": "MIT", "peer": true, "dependencies": { "parse5": "^6.0.1" @@ -8750,8 +8896,7 @@ }, "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT", "peer": true }, "node_modules/parseurl": { @@ -8851,9 +8996,8 @@ }, "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", "peer": true, "dependencies": { "find-up": "^4.0.0" @@ -8864,9 +9008,8 @@ }, "node_modules/pkg-dir/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", "peer": true, "dependencies": { "locate-path": "^5.0.0", @@ -8878,9 +9021,8 @@ }, "node_modules/pkg-dir/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", "peer": true, "dependencies": { "p-locate": "^4.1.0" @@ -8891,9 +9033,8 @@ }, "node_modules/pkg-dir/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", "peer": true, "dependencies": { "p-try": "^2.0.0" @@ -8907,9 +9048,8 @@ }, "node_modules/pkg-dir/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", "peer": true, "dependencies": { "p-limit": "^2.2.0" @@ -8935,9 +9075,9 @@ } }, "node_modules/prettier": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz", - "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true, "peer": true, "bin": { @@ -8962,11 +9102,10 @@ } }, "node_modules/pretty-format": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", - "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", + "version": "29.5.0", + "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", + "@jest/schemas": "^29.4.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -8990,9 +9129,8 @@ }, "node_modules/prompts": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "kleur": "^3.0.3", @@ -9042,8 +9180,6 @@ }, "node_modules/pure-rand": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", "dev": true, "funding": [ { @@ -9055,8 +9191,18 @@ "url": "https://opencollective.com/fast-check" } ], + "license": "MIT", "peer": true }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, "node_modules/qs": { "version": "6.11.0", "license": "BSD-3-Clause", @@ -9192,6 +9338,23 @@ "node": ">= 0.10" } }, + "node_modules/redis": { + "version": "4.6.7", + "license": "MIT", + "optional": true, + "peer": true, + "workspaces": [ + "./packages/*" + ], + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.5.8", + "@redis/graph": "1.1.0", + "@redis/json": "1.0.4", + "@redis/search": "1.1.3", + "@redis/time-series": "1.0.4" + } + }, "node_modules/redis-errors": { "version": "1.2.0", "license": "MIT", @@ -9211,14 +9374,12 @@ }, "node_modules/reflect-metadata": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "license": "Apache-2.0", "peer": true }, "node_modules/regenerator-runtime": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", - "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "version": "0.13.11", + "license": "MIT", "peer": true }, "node_modules/regexp.prototype.flags": { @@ -9293,9 +9454,8 @@ }, "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", "peer": true, "dependencies": { "resolve-from": "^5.0.0" @@ -9306,9 +9466,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", "peer": true, "engines": { "node": ">=8" @@ -9331,9 +9490,8 @@ }, "node_modules/resolve.exports": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10" @@ -9597,9 +9755,8 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.5.3", + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -9684,8 +9841,7 @@ }, "node_modules/sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", "peer": true, "dependencies": { "inherits": "^2.0.1", @@ -9746,9 +9902,8 @@ }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/slash": { @@ -9767,6 +9922,11 @@ "ws": "^8.8.1" } }, + "node_modules/snappyjs": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/snappyjs/-/snappyjs-0.6.1.tgz", + "integrity": "sha512-YIK6I2lsH072UE0aOFxxY1dPDCS43I5ktqHpeAsuLNYWkE5pGxRGWfDM4/vSUfNzXjC1Ivzt3qx31PCLmc9yqg==" + }, "node_modules/source-map": { "version": "0.6.1", "license": "BSD-3-Clause", @@ -9845,9 +10005,8 @@ }, "node_modules/string-length": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "char-regex": "^1.0.2", @@ -10173,8 +10332,7 @@ }, "node_modules/thenify": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", "peer": true, "dependencies": { "any-promise": "^1.0.0" @@ -10182,8 +10340,7 @@ }, "node_modules/thenify-all": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", "peer": true, "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -10192,6 +10349,29 @@ "node": ">=0.8" } }, + "node_modules/thrift": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/thrift/-/thrift-0.16.0.tgz", + "integrity": "sha512-W8DpGyTPlIaK3f+e1XOCLxefaUWXtrOXAaVIDbfYhmVyriYeAKgsBVFNJUV1F9SQ2SPt2sG44AZQxSGwGj/3VA==", + "dependencies": { + "browser-or-node": "^1.2.1", + "isomorphic-ws": "^4.0.1", + "node-int64": "^0.4.0", + "q": "^1.5.0", + "ws": "^5.2.3" + }, + "engines": { + "node": ">= 10.18.0" + } + }, + "node_modules/thrift/node_modules/ws": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", + "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -10225,8 +10405,7 @@ }, "node_modules/tmpl": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" + "license": "BSD-3-Clause" }, "node_modules/to-fast-properties": { "version": "2.0.0", @@ -10501,8 +10680,7 @@ }, "node_modules/type-detect": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -10547,9 +10725,8 @@ "license": "MIT" }, "node_modules/typeorm": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.17.tgz", - "integrity": "sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==", + "version": "0.3.16", + "license": "MIT", "peer": true, "dependencies": { "@sqltools/formatter": "^1.2.5", @@ -10654,8 +10831,7 @@ }, "node_modules/typeorm/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", "peer": true, "dependencies": { "balanced-match": "^1.0.0" @@ -10663,8 +10839,7 @@ }, "node_modules/typeorm/node_modules/glob": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "license": "ISC", "peer": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -10682,8 +10857,7 @@ }, "node_modules/typeorm/node_modules/minimatch": { "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", "peer": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -10694,8 +10868,7 @@ }, "node_modules/typeorm/node_modules/mkdirp": { "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "license": "MIT", "peer": true, "bin": { "mkdirp": "dist/cjs/src/bin.js" @@ -10847,9 +11020,8 @@ }, "node_modules/v8-to-istanbul": { "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, + "license": "ISC", "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", @@ -10860,22 +11032,35 @@ "node": ">=10.12.0" } }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.18", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" } }, "node_modules/v8-to-istanbul/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==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/validator": { @@ -10885,6 +11070,11 @@ "node": ">= 0.10" } }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==" + }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", @@ -10899,12 +11089,16 @@ }, "node_modules/walker": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" } }, + "node_modules/wasm-brotli": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/wasm-brotli/-/wasm-brotli-2.0.2.tgz", + "integrity": "sha512-DgjRlpZz9z5br4TjQHSlDHRF9NIuGXHUj3AqO08koDCXz7EYzmPDb7T/6oar5UKLYgPp9Yxc2ImGpx4BMFwbNQ==" + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -11224,6 +11418,11 @@ "node": ">=0.4" } }, + "node_modules/xxhash-wasm": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", + "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==" + }, "node_modules/y18n": { "version": "5.0.8", "license": "ISC", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index c2030f78..61c4b2a3 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -32,6 +32,7 @@ }, "homepage": "https://github.com/AmplicaLabs/content-publishing-service#readme", "dependencies": { + "@dsnp/parquetjs": "^1.3.4", "@frequency-chain/api-augment": "1.7.0", "@jest/globals": "^29.5.0", "@liaoliaots/nestjs-redis": "^9.0.5", From 45f4f4aa49477b1b7b6e4ec51097d6aa9956da64 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 5 Sep 2023 13:22:31 -0500 Subject: [PATCH 011/137] ipfs service class util --- .../apps/api/src/config/config.service.ts | 28 ++++++ .../libs/common/src/utils/ipfs.client.ts | 90 +++++++++++++++++++ services/content-watcher/package-lock.json | 29 +++++- services/content-watcher/package.json | 4 + 4 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 services/content-watcher/libs/common/src/utils/ipfs.client.ts diff --git a/services/content-watcher/apps/api/src/config/config.service.ts b/services/content-watcher/apps/api/src/config/config.service.ts index ff789c50..c9df4853 100644 --- a/services/content-watcher/apps/api/src/config/config.service.ts +++ b/services/content-watcher/apps/api/src/config/config.service.ts @@ -7,6 +7,10 @@ import { ConfigService as NestConfigService } from '@nestjs/config'; import { ICapacityLimit } from '../interfaces/capacity-limit.interface'; export interface ConfigEnvironmentVariables { + IPFS_ENDPOINT: URL; + IPFS_GATEWAY_URL: URL; + IPFS_BASIC_AUTH_USER: string; + IPFS_BASIC_AUTH_SECRET: string; REDIS_URL: URL; FREQUENCY_URL: URL; PROVIDER_ID: string; @@ -77,4 +81,28 @@ export class ConfigService { public getCapacityLimit(): ICapacityLimit { return this.capacityLimit; } + + public getIpfsEndpoint(): string { + return this.nestConfigService.get('IPFS_ENDPOINT')!; + } + + public getIpfsGatewayUrl(): string { + return this.nestConfigService.get('IPFS_GATEWAY_URL')!; + } + + public getIpfsBasicAuthUser(): string { + return this.nestConfigService.get('IPFS_BASIC_AUTH_USER')!; + } + + public getIpfsBasicAuthSecret(): string { + return this.nestConfigService.get('IPFS_BASIC_AUTH_SECRET')!; + } + + public getIpfsCidPlaceholder(cid): string { + const gatewayUrl = this.getIpfsGatewayUrl(); + if (!gatewayUrl || !gatewayUrl.includes('[CID]')) { + return `https://ipfs.io/ipfs/${cid}`; + } + return gatewayUrl.replace('[CID]', cid); + } } diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts new file mode 100644 index 00000000..91159a53 --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -0,0 +1,90 @@ +// ipfs.service.ts + +import { Injectable, Logger } from '@nestjs/common'; +import axios from 'axios'; +import FormData from 'form-data'; +import { extension as getExtension } from 'mime-types'; +import { CID } from 'multiformats/cid'; +import { blake2b256 as hasher } from '@multiformats/blake2/blake2b'; +import { base58btc } from 'multiformats/bases/base58'; +import { create } from 'multiformats/hashes/digest'; +import { ConfigService } from '../../../../apps/api/src/config/config.service'; + +export interface FilePin { + cid: string; + cidBytes: Uint8Array; + fileName: string; + size: number; + hash: string; +} + +@Injectable() +export class IpfsService { + logger: Logger; + + constructor(private readonly configService: ConfigService) { + this.logger = new Logger(IpfsService.name); + } + + private async ipfsPinBuffer(filename: string, contentType: string, fileBuffer: Buffer): Promise { + const ipfsAdd = `${this.configService.getIpfsEndpoint()}/api/v0/add`; + const form = new FormData(); + form.append('file', fileBuffer, { + filename, + contentType, + }); + + const ipfsAuthUser = this.configService.getIpfsBasicAuthUser(); + const ipfsAuthSecret = this.configService.getIpfsBasicAuthSecret(); + const ipfsAuth = ipfsAuthUser && ipfsAuthSecret ? `Basic ${Buffer.from(`${ipfsAuthUser}:${ipfsAuthSecret}`).toString('base64')}` : ''; + + const headers = { + 'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`, + Accept: '*/*', + Connection: 'keep-alive', + authorization: ipfsAuth, + }; + + const response = await axios.post(ipfsAdd, form, { headers }); + + const { data } = response; + if (!data || !data.Hash || !data.Size) { + throw new Error(`Unable to pin file: ${filename}`); + } + const cid = CID.parse(data.Hash).toV1(); + + this.logger.debug(`Pinned file: ${filename} with size: ${data.Size} and cid: ${cid.toString(base58btc)}`); + + return { + cid: cid.toString(base58btc), + cidBytes: cid.bytes, + fileName: data.Name, + size: data.Size, + hash: '', + }; + } + + public async ipfsPin(mimeType: string, file: Buffer): Promise { + const hash = await this.ipfsHashBuffer(file); + const extension = getExtension(mimeType); + if (extension === false) { + throw new Error(`unknown mimetype: ${mimeType}`); + } + const ipfs = await this.ipfsPinBuffer(`${hash}.${extension}`, mimeType, file); + return { ...ipfs, hash }; + } + + private async ipfsHashBuffer(fileBuffer: Buffer): Promise { + this.logger.debug(`Hashing file buffer with length: ${fileBuffer.length}`); + const hashed = await hasher.digest(fileBuffer); + const hash = create(hasher.code, hashed.bytes); + return base58btc.encode(hash.bytes); + } + + public ipfsUrl(cid: string): string { + if (this.configService.getIpfsGatewayUrl().includes('[CID]')) { + return this.configService.getIpfsGatewayUrl().replace('[CID]', cid); + } + return `${this.configService.getIpfsGatewayUrl()}/ipfs/${cid}`; + } +} diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index d6f3ede8..543aa1f9 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -13,6 +13,7 @@ "@frequency-chain/api-augment": "1.7.0", "@jest/globals": "^29.5.0", "@liaoliaots/nestjs-redis": "^9.0.5", + "@multiformats/blake2": "^1.0.13", "@nestjs/axios": "^2.0.0", "@nestjs/bullmq": "^10.0.0", "@nestjs/cli": "^10.1.14", @@ -38,8 +39,11 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "crypto": "^1.0.1", + "form-data": "^4.0.0", "ioredis": "^5.3.2", "joi": "^17.9.1", + "mime-types": "^2.1.35", + "multiformats": "^9.9.0", "rxjs": "^7.8.1", "time-constants": "^1.0.3" }, @@ -1389,6 +1393,15 @@ "node": ">=8" } }, + "node_modules/@multiformats/blake2": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@multiformats/blake2/-/blake2-1.0.13.tgz", + "integrity": "sha512-T1Kzya0wjj85CaVeRSpJ858EnSvW1pw94GSitxYf84VsNdv5XYbJ6QG8y26Ft1bVALzrUCmqkQrR53QHSyu6RA==", + "dependencies": { + "blakejs": "^1.1.1", + "multiformats": "^9.5.4" + } + }, "node_modules/@nestjs/axios": { "version": "2.0.0", "license": "MIT", @@ -3988,6 +4001,11 @@ "node": ">= 6" } }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" + }, "node_modules/bn.js": { "version": "5.2.1", "license": "MIT" @@ -6221,7 +6239,8 @@ }, "node_modules/form-data": { "version": "4.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -8346,7 +8365,8 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { "mime-db": "1.52.0" }, @@ -8465,6 +8485,11 @@ "node": ">= 6.0.0" } }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 61c4b2a3..46b470d1 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -36,6 +36,7 @@ "@frequency-chain/api-augment": "1.7.0", "@jest/globals": "^29.5.0", "@liaoliaots/nestjs-redis": "^9.0.5", + "@multiformats/blake2": "^1.0.13", "@nestjs/axios": "^2.0.0", "@nestjs/bullmq": "^10.0.0", "@nestjs/cli": "^10.1.14", @@ -61,8 +62,11 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "crypto": "^1.0.1", + "form-data": "^4.0.0", "ioredis": "^5.3.2", "joi": "^17.9.1", + "mime-types": "^2.1.35", + "multiformats": "^9.9.0", "rxjs": "^7.8.1", "time-constants": "^1.0.3" }, From 5efa6bb8618291323a67d67063214c405fc3f210 Mon Sep 17 00:00:00 2001 From: Aramik Date: Tue, 5 Sep 2023 16:51:16 -0700 Subject: [PATCH 012/137] init asset upload (#27) * init asset upload * more progress * fixed wrong queue name bug --- .../.github/workflows/build.yml | 2 +- .../apps/api/src/api.controller.ts | 13 +- .../apps/api/src/api.module.ts | 3 + .../apps/api/src/api.service.ts | 120 +- services/content-watcher/apps/api/src/main.ts | 3 +- .../apps/api/test/app.e2e-spec.ts | 71 +- .../asset_processor/asset.processor.module.ts | 57 + .../asset.processor.service.ts | 49 + .../src/batch_announcer/ipfs.announcer.ts | 8 +- .../request.processor.module.ts | 60 + .../request.processor.service.ts | 78 ++ .../apps/worker/src/worker.module.ts | 8 +- .../libs/common/src/dtos/announcement.dto.ts | 1 + .../libs/common/src/dtos/validation.dto.ts | 6 + .../src/interfaces/asset-job.interface.ts | 12 + .../src/interfaces/request-job.interface.ts | 1 + .../libs/common/src/utils/ipfs.client.ts | 32 +- .../libs/common/src/utils/ipfs.ts | 16 + .../libs/common/src/utils/queues.ts | 4 + .../libs/common/src/utils/redis.ts | 12 + services/content-watcher/nest-cli.json | 18 +- services/content-watcher/package-lock.json | 1071 +++++++++++++++-- services/content-watcher/package.json | 19 +- services/content-watcher/tsconfig.build.json | 7 + services/content-watcher/tsconfig.json | 14 +- 25 files changed, 1484 insertions(+), 201 deletions(-) create mode 100644 services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts create mode 100644 services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts create mode 100644 services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts create mode 100644 services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts create mode 100644 services/content-watcher/libs/common/src/interfaces/asset-job.interface.ts create mode 100644 services/content-watcher/libs/common/src/utils/ipfs.ts create mode 100644 services/content-watcher/libs/common/src/utils/redis.ts create mode 100644 services/content-watcher/tsconfig.build.json diff --git a/services/content-watcher/.github/workflows/build.yml b/services/content-watcher/.github/workflows/build.yml index 2a456a84..84a60224 100644 --- a/services/content-watcher/.github/workflows/build.yml +++ b/services/content-watcher/.github/workflows/build.yml @@ -56,4 +56,4 @@ jobs: run: npm ci - name: License Check # List all the licenses and error out if it is not one of the supported licenses - run: npx license-report --fields=name --fields=licenseType | jq 'map(select(.licenseType | IN("MIT", "Apache-2.0", "ISC", "BSD-3-Clause", "BSD-2-Clause") | not)) | if length == 0 then halt else halt_error(1) end' + run: npx license-report --fields=name --fields=licenseType | jq 'map(select(.licenseType | IN("MIT", "Apache-2.0", "ISC", "BSD-3-Clause", "BSD-2-Clause", "(Apache-2.0 AND MIT)") | not)) | if length == 0 then halt else halt_error(1) end' diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 12144e64..ea7518f6 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,10 +1,11 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Logger, Param, ParseFilePipeBuilder, Post, Put, UploadedFiles, UseInterceptors } from '@nestjs/common'; -import { v4 as uuidv4 } from 'uuid'; import { FilesInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes } from '@nestjs/swagger'; +import { ApiService } from './api.service'; import { AnnouncementResponseDto, AnnouncementTypeDto, + AssetIncludedRequestDto, BroadcastDto, DSNP_VALID_MIME_TYPES, DsnpContentHashParam, @@ -16,7 +17,6 @@ import { UpdateDto, UploadResponseDto, } from '../../../libs/common/src'; -import { ApiService } from './api.service'; @Controller('api') export class ApiController { @@ -59,21 +59,20 @@ export class ApiController { files: // eslint-disable-next-line no-undef Array, ): Promise { - this.logger.log(`upload ${files.length}`); - return { - assetIds: files.map((_) => uuidv4()), - }; + return this.apiService.addAssets(files); } @Post('content/:userDsnpId/broadcast') @HttpCode(202) async broadcast(@Param() userDsnpId: DsnpUserIdParam, @Body() broadcastDto: BroadcastDto): Promise { + await this.apiService.validateAssets(broadcastDto as AssetIncludedRequestDto); return this.apiService.enqueueRequest(AnnouncementTypeDto.BROADCAST, userDsnpId.userDsnpId, broadcastDto); } @Post('content/:userDsnpId/reply') @HttpCode(202) async reply(@Param() userDsnpId: DsnpUserIdParam, @Body() replyDto: ReplyDto): Promise { + await this.apiService.validateAssets(replyDto as AssetIncludedRequestDto); return this.apiService.enqueueRequest(AnnouncementTypeDto.REPLY, userDsnpId.userDsnpId, replyDto); } @@ -86,6 +85,7 @@ export class ApiController { @Put('content/:userDsnpId/:targetContentHash') @HttpCode(202) async update(@Param() userDsnpId: DsnpUserIdParam, @Param() targetContentHash: DsnpContentHashParam, @Body() updateDto: UpdateDto): Promise { + await this.apiService.validateAssets(updateDto as AssetIncludedRequestDto); return this.apiService.enqueueRequest(AnnouncementTypeDto.UPDATE, userDsnpId.userDsnpId, updateDto, targetContentHash.targetContentHash); } @@ -98,6 +98,7 @@ export class ApiController { @Put('profile/:userDsnpId') @HttpCode(202) async profile(@Param() userDsnpId: DsnpUserIdParam, @Body() profileDto: ProfileDto): Promise { + await this.apiService.validateAssets(profileDto as AssetIncludedRequestDto); return this.apiService.enqueueRequest(AnnouncementTypeDto.PROFILE, userDsnpId.userDsnpId, profileDto); } } diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index b7f45e39..83492bd7 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -20,6 +20,9 @@ import { ApiService } from './api.service'; BullModule.registerQueue({ name: QueueConstants.REQUEST_QUEUE_NAME, }), + BullModule.registerQueue({ + name: QueueConstants.ASSET_QUEUE_NAME, + }), ConfigModule, RedisModule.forRootAsync( { diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 5ca051c4..ee1abbc9 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -1,14 +1,36 @@ -import { Injectable, Logger } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; +import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import { Queue } from 'bullmq'; import { createHash } from 'crypto'; -import { AnnouncementResponseDto, AnnouncementTypeDto, IRequestJob, QueueConstants, RequestTypeDto } from '../../../libs/common/src'; +import { BulkJobOptions } from 'bullmq/dist/esm/interfaces'; +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import Redis from 'ioredis'; +import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util'; +import { + AnnouncementResponseDto, + AnnouncementTypeDto, + AssetIncludedRequestDto, + IRequestJob, + isImage, + QueueConstants, + RequestTypeDto, + UploadResponseDto, +} from '../../../libs/common/src'; +import { calculateDsnpHash, calculateIpfsCID } from '../../../libs/common/src/utils/ipfs'; +import { IAssetJob, IAssetMetadata } from '../../../libs/common/src/interfaces/asset-job.interface'; +import { RedisUtils } from '../../../libs/common/src/utils/redis'; +import getAssetDataKey = RedisUtils.getAssetDataKey; +import getAssetMetadataKey = RedisUtils.getAssetMetadataKey; @Injectable() export class ApiService { private readonly logger: Logger; - constructor(@InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue) { + constructor( + @InjectRedis() private redis: Redis, + @InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue, + @InjectQueue(QueueConstants.ASSET_QUEUE_NAME) private assetQueue: Queue, + ) { this.logger = new Logger(this.constructor.name); } @@ -19,6 +41,7 @@ export class ApiService { announcementType, dsnpUserId, targetContentHash, + dependencyAttempt: 0, } as IRequestJob; data.id = this.calculateJobId(data); const job = await this.requestQueue.add(`Request Job - ${data.id}`, data, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); // TODO: should come from config @@ -28,6 +51,97 @@ export class ApiService { }; } + async validateAssets(content: AssetIncludedRequestDto): Promise { + const checkingList: Array<{ onlyImage: boolean; referenceId: string }> = []; + if (content.profile) { + content.profile.icon?.forEach((reference) => checkingList.push({ onlyImage: true, referenceId: reference.referenceId })); + } else if (content.content) { + content.content.assets?.forEach( + (asset) => + asset.references?.forEach((reference) => + checkingList.push({ + onlyImage: false, + referenceId: reference.referenceId, + }), + ), + ); + } + + const redisResults = await Promise.all(checkingList.map((obj) => this.redis.get(getAssetMetadataKey(obj.referenceId)))); + const errors: string[] = []; + redisResults.forEach((res, index) => { + if (res === null) { + errors.push(`${content.profile ? 'profile.icon' : 'content.assets'}.referenceId ${checkingList[index].referenceId} does not exist!`); + } else if (checkingList[index].onlyImage) { + // checks if attached asset is an image + const metadata: IAssetMetadata = JSON.parse(res); + if (!isImage(metadata.mimeType)) { + errors.push(`profile.icon.referenceId ${checkingList[index].referenceId} is not an image!`); + } + } + }); + if (errors.length > 0) { + throw new HttpErrorByCode[400](errors); + } + } + + // TODO: make all these operations transactional + // eslint-disable-next-line no-undef,class-methods-use-this + async addAssets(files: Array): Promise { + // calculate ipfs cid references + const referencePromises: Promise[] = files.map((file) => calculateIpfsCID(file.buffer)); + const references = await Promise.all(referencePromises); + + // add assets to redis + const redisDataOps = files.map((f, index) => this.redis.set(getAssetDataKey(references[index]), f.buffer)); + const addedData = await Promise.all(redisDataOps); + this.logger.debug(addedData); + + // add asset jobs to the queue + const jobs: any[] = []; + files.forEach((f, index) => { + jobs.push({ + name: `Asset Job - ${references[index]}`, + data: { + ipfsCid: references[index], + contentLocation: getAssetDataKey(references[index]), + metadataLocation: getAssetMetadataKey(references[index]), + mimeType: f.mimetype, + } as IAssetJob, + opts: { + jobId: references[index], + removeOnFail: false, + removeOnComplete: true, + attempts: 3, + backoff: { + type: 'exponential', + delay: 10000, + }, + } as BulkJobOptions, + }); + }); + const queuedJobs = await this.assetQueue.addBulk(jobs); + this.logger.debug(queuedJobs); + + // add metadata to redis + const redisMetadataOps = files.map((f, index) => + this.redis.set( + getAssetMetadataKey(references[index]), + JSON.stringify({ + ipfsCid: references[index], + mimeType: f.mimetype, + createdOn: Date.now(), + } as IAssetMetadata), + ), + ); + const addedMetadata = await Promise.all(redisMetadataOps); + this.logger.debug(addedMetadata); + + return { + assetIds: references, + }; + } + // eslint-disable-next-line class-methods-use-this calculateJobId(jobWithoutId: IRequestJob): string { const stringVal = JSON.stringify(jobWithoutId); diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index 8cba5acd..719af90b 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -1,6 +1,7 @@ import { NestFactory } from '@nestjs/core'; -import { Logger, ValidationPipe } from '@nestjs/common'; +import { BadRequestException, Logger, ValidationPipe } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ValidationError } from 'class-validator'; import { ApiModule } from './api.module'; import { initSwagger } from './config/swagger_config'; diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index b6786248..b763942c 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -9,7 +9,7 @@ import { ApiModule } from '../src/api.module'; describe('AppController E2E request verification!', () => { let app: INestApplication; let module: TestingModule; - const validlocation = { + const validLocation = { name: 'name of location', accuracy: 97, altitude: 10, @@ -28,36 +28,18 @@ describe('AppController E2E request verification!', () => { name: '#taggedUser', }, ]; - const validContent = { + const validContentNoAssets = { content: 'test broadcast message', published: '1970-01-01T00:00:00+00:00', - assets: [ - { - type: 'image', - name: 'image asset', - references: [ - { - referenceId: 'reference-id-1', - height: 123, - width: 321, - }, - ], - }, - { - type: 'link', - name: 'link asset', - href: 'http://example.com', - }, - ], name: 'name of note content', tag: validTags, - location: validlocation, + location: validLocation, }; - const validBroadCast = { - content: validContent, + const validBroadCastNoAssets = { + content: validContentNoAssets, }; - const validReply = { - content: validContent, + const validReplyNoAssets = { + content: validContentNoAssets, inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', }; const validReaction = { @@ -65,19 +47,12 @@ describe('AppController E2E request verification!', () => { apply: 5, inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', }; - const validProfile = { - icon: [ - { - referenceId: 'reference-id-1', - height: 123, - width: 321, - }, - ], + const validProfileNoAssets = { summary: 'profile summary', published: '1970-01-01T00:00:00+00:00', name: 'name of profile content', tag: validTags, - location: validlocation, + location: validLocation, }; beforeEach(async () => { @@ -102,7 +77,7 @@ describe('AppController E2E request verification!', () => { const invalidDsnpUserId = '2gsjhdaj'; return request(app.getHttpServer()) .post(`/api/content/${invalidDsnpUserId}/broadcast`) - .send(validBroadCast) + .send(validBroadCastNoAssets) .expect(400) .expect((res) => expect(res.text).toContain('must be a number string')); }); @@ -117,10 +92,10 @@ describe('AppController E2E request verification!', () => { }); describe('(POST) /api/content/:dsnpUserId/broadcast', () => { - it('valid request should work!', () => + it('valid request without assets should work!', () => request(app.getHttpServer()) .post(`/api/content/123/broadcast`) - .send(validBroadCast) + .send(validBroadCastNoAssets) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); @@ -371,10 +346,10 @@ describe('AppController E2E request verification!', () => { }); describe('(POST) /api/content/:dsnpUserId/reply', () => { - it('valid request should work!', () => + it('valid request without assets should work!', () => request(app.getHttpServer()) .post(`/api/content/123/reply`) - .send(validReply) + .send(validReplyNoAssets) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); @@ -391,7 +366,7 @@ describe('AppController E2E request verification!', () => { request(app.getHttpServer()) .post(`/api/content/123/reply`) .send({ - content: validContent, + content: validContentNoAssets, }) .expect(400) .expect((res) => expect(res.text).toContain('inReplyTo must be a string'))); @@ -400,7 +375,7 @@ describe('AppController E2E request verification!', () => { request(app.getHttpServer()) .post(`/api/content/123/reply`) .send({ - content: validContent, + content: validContentNoAssets, inReplyTo: 'shgdjas72gsjajasa', }) .expect(400) @@ -447,12 +422,12 @@ describe('AppController E2E request verification!', () => { }); describe('(PUT) /api/content/:dsnpUserId/:contentHash', () => { - it('valid request should work!', () => + it('valid request without assets should work!', () => request(app.getHttpServer()) .put(`/api/content/123/0x7653423447AF`) .send({ targetAnnouncementType: 'broadcast', - content: validContent, + content: validContentNoAssets, }) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); @@ -462,7 +437,7 @@ describe('AppController E2E request verification!', () => { .put(`/api/content/123/0x7653423447AF`) .send({ targetAnnouncementType: 'invalid', - content: validContent, + content: validContentNoAssets, }) .expect(400) .expect((res) => expect(res.text).toContain('targetAnnouncementType must be one of the following values'))); @@ -477,11 +452,11 @@ describe('AppController E2E request verification!', () => { }); describe('(PUT) /api/profile/:userDsnpId', () => { - it('valid request should work!', () => + it('valid request without assets should work!', () => request(app.getHttpServer()) .put(`/api/profile/123`) .send({ - profile: validProfile, + profile: validProfileNoAssets, }) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); @@ -527,14 +502,14 @@ describe('AppController E2E request verification!', () => { it('valid request should work!', () => request(app.getHttpServer()) .put(`/api/asset/upload`) - .attach('files', Buffer.from(validContent.toString()), 'image.jpg') + .attach('files', Buffer.from(validContentNoAssets.toString()), 'image.jpg') .expect(202) .expect((res) => expect(res.text).toContain('assetIds'))); it('invalid mime should fail', () => request(app.getHttpServer()) .put(`/api/asset/upload`) - .attach('files', Buffer.from(validContent.toString()), 'doc.txt') + .attach('files', Buffer.from(validContentNoAssets.toString()), 'doc.txt') .expect(422) .expect((res) => expect(res.text).toContain('expected type is'))); }); diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts new file mode 100644 index 00000000..22bd2282 --- /dev/null +++ b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts @@ -0,0 +1,57 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { BullModule } from '@nestjs/bullmq'; +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { ConfigModule } from '../../../api/src/config/config.module'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { QueueConstants } from '../../../../libs/common/src'; +import { AssetProcessorService } from './asset.processor.service'; +import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; + +@Module({ + imports: [ + ConfigModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue({ + name: QueueConstants.ASSET_QUEUE_NAME, + }), + ], + providers: [AssetProcessorService, IpfsService], + exports: [BullModule, AssetProcessorService, IpfsService], +}) +export class AssetProcessorModule {} diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts new file mode 100644 index 00000000..045850c0 --- /dev/null +++ b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts @@ -0,0 +1,49 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, Logger } from '@nestjs/common'; +import { Job } from 'bullmq'; +import Redis from 'ioredis'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { QueueConstants } from '../../../../libs/common/src'; +import { IAssetJob } from '../../../../libs/common/src/interfaces/asset-job.interface'; +import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; + +@Injectable() +@Processor(QueueConstants.ASSET_QUEUE_NAME) +export class AssetProcessorService extends WorkerHost { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private configService: ConfigService, + private ipfsService: IpfsService, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + async process(job: Job): Promise { + this.logger.log(`Processing job ${job.id} of type ${job.name}`); + this.logger.debug(job.asJSON()); + const redisResults = await this.redis.getBuffer(job.data.contentLocation); + if (!redisResults) { + throw new Error(`Content stored in ${job.data.contentLocation} does not exist!`); + } + const pinned = await this.ipfsService.ipfsPin(job.data.mimeType, redisResults, false); + this.logger.log(pinned); + if (job.data.ipfsCid !== pinned.cid) { + throw new Error(`Cid does not match ${job.data.ipfsCid} != ${pinned.cid}`); + } + } + + // eslint-disable-next-line class-methods-use-this + @OnWorkerEvent('completed') + async onCompleted(job: Job) { + this.logger.log(`completed ${job.id}`); + const secondsPassed = Math.round((Date.now() - job.timestamp) / 1000); + const expectedSecondsToExpire = 5 * 60; // TODO: get from config + const secondsToExpire = Math.max(0, expectedSecondsToExpire - secondsPassed); + const result = await this.redis.pipeline().del(job.data.contentLocation).expire(job.data.metadataLocation, secondsToExpire, 'LT').exec(); + this.logger.debug(result); + } +} diff --git a/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts index bc94d958..f548271e 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts @@ -1,6 +1,4 @@ import { Injectable, Logger } from '@nestjs/common'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { BlockchainService } from '../blockchain/blockchain.service'; import { ConfigService } from '../../../api/src/config/config.service'; import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; @@ -8,11 +6,7 @@ import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interf export class IPFSAnnouncer { private logger: Logger; - constructor( - private configService: ConfigService, - private blockchainService: BlockchainService, - private eventEmitter: EventEmitter2, - ) { + constructor(private configService: ConfigService) { this.logger = new Logger(IPFSAnnouncer.name); } diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts new file mode 100644 index 00000000..e50fdff1 --- /dev/null +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts @@ -0,0 +1,60 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { BullModule } from '@nestjs/bullmq'; +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { ConfigModule } from '../../../api/src/config/config.module'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { QueueConstants } from '../../../../libs/common/src'; +import { RequestProcessorService } from './request.processor.service'; +import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; + +@Module({ + imports: [ + ConfigModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue({ + name: QueueConstants.ASSET_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.REQUEST_QUEUE_NAME, + }), + ], + providers: [RequestProcessorService, IpfsService], + exports: [BullModule, RequestProcessorService, IpfsService], +}) +export class RequestProcessorModule {} diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts new file mode 100644 index 00000000..1abfca4f --- /dev/null +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts @@ -0,0 +1,78 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, Logger } from '@nestjs/common'; +import { DelayedError, Job } from 'bullmq'; +import Redis from 'ioredis'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { AnnouncementTypeDto, BroadcastDto, IRequestJob, ProfileDto, QueueConstants, ReplyDto, UpdateDto } from '../../../../libs/common/src'; +import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; + +@Injectable() +@Processor(QueueConstants.REQUEST_QUEUE_NAME) +export class RequestProcessorService extends WorkerHost { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private configService: ConfigService, + private ipfsService: IpfsService, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + async process(job: Job): Promise { + this.logger.log(`Processing job ${job.id} of type ${job.name}`); + this.logger.debug(job.asJSON()); + const assets = this.getAssetReferencesFromRequestJob(job.data); + + const pinnedAssets = assets.map((cid) => this.ipfsService.getPinned(cid)); + const pinnedResult = await Promise.all(pinnedAssets); + this.logger.debug(pinnedResult); + // if any of assets does not exists delay the job for a future attempt + if (pinnedResult.some((buffer) => !buffer)) { + await this.delayJobAndIncrementAttempts(job); + } else { + // TODO: create attachments from assets + } + } + + // eslint-disable-next-line class-methods-use-this + @OnWorkerEvent('completed') + onCompleted() {} + + // eslint-disable-next-line class-methods-use-this + private getAssetReferencesFromRequestJob(job: IRequestJob): Array { + const assets: string[] = []; + // eslint-disable-next-line default-case + switch (job.announcementType) { + case AnnouncementTypeDto.BROADCAST: + (job.content as BroadcastDto).content.assets?.forEach((a) => a.references?.forEach((r) => assets.push(r.referenceId))); + break; + case AnnouncementTypeDto.REPLY: + (job.content as ReplyDto).content.assets?.forEach((a) => a.references?.forEach((r) => assets.push(r.referenceId))); + break; + case AnnouncementTypeDto.UPDATE: + (job.content as UpdateDto).content.assets?.forEach((a) => a.references?.forEach((r) => assets.push(r.referenceId))); + break; + case AnnouncementTypeDto.PROFILE: + (job.content as ProfileDto).profile.icon?.forEach((r) => assets.push(r.referenceId)); + } + return assets; + } + + // eslint-disable-next-line class-methods-use-this + private async delayJobAndIncrementAttempts(job: Job) { + const { data } = job; + data.dependencyAttempt += 1; + if (data.dependencyAttempt <= 3) { + // attempts 10 seconds, 20 seconds, 40 seconds + const delayedTime = 2 ** data.dependencyAttempt * 5 * 1000; + await job.moveToDelayed(Date.now() + delayedTime, job.token); // TODO: get from config + await job.update(data); + throw new DelayedError(); + } else { + throw new Error('Dependency failed!'); + } + } +} diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts index ea631a37..62ce5776 100644 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -13,6 +13,10 @@ import { BatchAnnouncementService } from './batch_announcer/batch.announcer.serv import { BatchAnnouncerModule } from './batch_announcer/batch.announcer.module'; import { StatusMonitorModule } from './monitor/status.monitor.module'; import { StatusMonitoringService } from './monitor/status.monitor.service'; +import { AssetProcessorModule } from './asset_processor/asset.processor.module'; +import { AssetProcessorService } from './asset_processor/asset.processor.service'; +import { RequestProcessorModule } from './request_processor/request.processor.module'; +import { RequestProcessorService } from './request_processor/request.processor.service'; @Module({ imports: [ @@ -51,7 +55,9 @@ import { StatusMonitoringService } from './monitor/status.monitor.service'; BlockchainModule, BatchAnnouncerModule, StatusMonitorModule, + AssetProcessorModule, + RequestProcessorModule, ], - providers: [BatchAnnouncementService, ConfigService, WorkerService, PublishingService, StatusMonitoringService], + providers: [BatchAnnouncementService, ConfigService, WorkerService, PublishingService, StatusMonitoringService, AssetProcessorService, RequestProcessorService], }) export class WorkerModule {} diff --git a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts index 857d463f..d9464527 100644 --- a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts @@ -65,3 +65,4 @@ export class ProfileDto { } export type RequestTypeDto = BroadcastDto | ReplyDto | ReactionDto | UpdateDto | ProfileDto; +export type AssetIncludedRequestDto = BroadcastDto & ReplyDto & UpdateDto & ProfileDto; diff --git a/services/content-watcher/libs/common/src/dtos/validation.dto.ts b/services/content-watcher/libs/common/src/dtos/validation.dto.ts index b867b109..3b2e0fee 100644 --- a/services/content-watcher/libs/common/src/dtos/validation.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/validation.dto.ts @@ -30,3 +30,9 @@ export const DURATION_REGEX = /^-?P(([0-9]+Y)?([0-9]+M)?([0-9]+D)?(T([0-9]+H)?([ */ export const DSNP_VALID_MIME_TYPES = /(image\/jpeg|image\/png|image\/svg\+xml|image\/webp|image\/gif|video\/mpeg|video\/ogg|video\/webm|video\/H256|video\/mp4|audio\/mpeg|audio\/ogg|audio\/webm)$/; +/** + * checks to see if provided mime type is an image + */ +export function isImage(mimeType: string): boolean { + return mimeType != null && mimeType.toLowerCase().startsWith('image'); +} diff --git a/services/content-watcher/libs/common/src/interfaces/asset-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/asset-job.interface.ts new file mode 100644 index 00000000..3da2dc91 --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/asset-job.interface.ts @@ -0,0 +1,12 @@ +export interface IAssetJob { + ipfsCid: string; + mimeType: string; + contentLocation: string; + metadataLocation: string; +} + +export interface IAssetMetadata { + ipfsCid: string; + mimeType: string; + createdOn: number; +} diff --git a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts index 0798cd4a..55593ec5 100644 --- a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts @@ -7,4 +7,5 @@ export interface IRequestJob { dsnpUserId: string; targetContentHash?: string; content?: RequestTypeDto; + dependencyAttempt: number; } diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index 91159a53..0729d642 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -8,6 +8,7 @@ import { CID } from 'multiformats/cid'; import { blake2b256 as hasher } from '@multiformats/blake2/blake2b'; import { base58btc } from 'multiformats/bases/base58'; import { create } from 'multiformats/hashes/digest'; +import { randomUUID } from 'crypto'; import { ConfigService } from '../../../../apps/api/src/config/config.service'; export interface FilePin { @@ -53,10 +54,10 @@ export class IpfsService { } const cid = CID.parse(data.Hash).toV1(); - this.logger.debug(`Pinned file: ${filename} with size: ${data.Size} and cid: ${cid.toString(base58btc)}`); + this.logger.debug(`Pinned file: ${filename} with size: ${data.Size} and cid: ${cid}`); return { - cid: cid.toString(base58btc), + cid: cid.toString(), cidBytes: cid.bytes, fileName: data.Name, size: data.Size, @@ -64,14 +65,33 @@ export class IpfsService { }; } - public async ipfsPin(mimeType: string, file: Buffer): Promise { - const hash = await this.ipfsHashBuffer(file); + public async ipfsPin(mimeType: string, file: Buffer, calculateDsnpHash: boolean = true): Promise { + const fileName = calculateDsnpHash ? await this.ipfsHashBuffer(file) : randomUUID().toString(); const extension = getExtension(mimeType); if (extension === false) { throw new Error(`unknown mimetype: ${mimeType}`); } - const ipfs = await this.ipfsPinBuffer(`${hash}.${extension}`, mimeType, file); - return { ...ipfs, hash }; + const ipfs = await this.ipfsPinBuffer(`${fileName}.${extension}`, mimeType, file); + return { ...ipfs, hash: calculateDsnpHash ? fileName : '' }; + } + + // TODO: bugfix: when the cid does not exist the endpoint will get stuck indefinitely + public async getPinned(cid: string): Promise { + const ipfsGet = `${this.configService.getIpfsEndpoint()}/api/v0/cat?arg=${cid}`; + const ipfsAuthUser = this.configService.getIpfsBasicAuthUser(); + const ipfsAuthSecret = this.configService.getIpfsBasicAuthSecret(); + const ipfsAuth = ipfsAuthUser && ipfsAuthSecret ? `Basic ${Buffer.from(`${ipfsAuthUser}:${ipfsAuthSecret}`).toString('base64')}` : ''; + + const headers = { + Accept: '*/*', + Connection: 'keep-alive', + authorization: ipfsAuth, + }; + + const response = await axios.post(ipfsGet, null, { headers, responseType: 'arraybuffer' }); + + const { data } = response; + return data; } private async ipfsHashBuffer(fileBuffer: Buffer): Promise { diff --git a/services/content-watcher/libs/common/src/utils/ipfs.ts b/services/content-watcher/libs/common/src/utils/ipfs.ts new file mode 100644 index 00000000..48e5648c --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/ipfs.ts @@ -0,0 +1,16 @@ +import * as ipfsHash from 'ipfs-only-hash'; +import { blake2b256 } from '@multiformats/blake2/blake2b'; +import { create } from 'multiformats/hashes/digest'; +import { CID } from 'multiformats'; +import { base58btc } from 'multiformats/bases/base58'; + +export async function calculateIpfsCID(buffer: Buffer): Promise { + const v0 = await ipfsHash.of(buffer); + return CID.parse(v0).toV1().toString(); +} + +export const calculateDsnpHash = async (fileBuffer: Buffer): Promise => { + const hash = await blake2b256.digest(fileBuffer); + const digest = create(blake2b256.code, hash.bytes); + return base58btc.encode(digest.bytes); +}; diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index 2db48f4a..270e6eed 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -1,6 +1,10 @@ import { AnnouncementTypeDto } from '../dtos/common.dto'; export namespace QueueConstants { + /** + * Name of the queue that has all incoming asset uploads + */ + export const ASSET_QUEUE_NAME = 'assetQueue'; /** * Name of the queue that has all incoming requests */ diff --git a/services/content-watcher/libs/common/src/utils/redis.ts b/services/content-watcher/libs/common/src/utils/redis.ts new file mode 100644 index 00000000..c16f4169 --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/redis.ts @@ -0,0 +1,12 @@ +export namespace RedisUtils { + const ASSET_DATA_KEY_PREFIX = 'asset::data'; + const ASSET_METADATA_KEY_PREFIX = 'asset::metadata'; + + export function getAssetDataKey(assetId: string) { + return `${ASSET_DATA_KEY_PREFIX}:${assetId}`; + } + + export function getAssetMetadataKey(assetId: string) { + return `${ASSET_METADATA_KEY_PREFIX}:${assetId}`; + } +} diff --git a/services/content-watcher/nest-cli.json b/services/content-watcher/nest-cli.json index dca51c9f..3478f17a 100644 --- a/services/content-watcher/nest-cli.json +++ b/services/content-watcher/nest-cli.json @@ -1,4 +1,14 @@ { + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "apps/api/src", + "compilerOptions": { + "deleteOutDir": true, + "webpack": true, + "tsConfigPath": "apps/api/tsconfig.app.json" + }, + "monorepo": true, + "root": "apps/api", "projects": { "common": { "type": "library", @@ -27,11 +37,5 @@ "tsConfigPath": "apps/worker/tsconfig.app.json" } } - }, - "compilerOptions": { - "tsConfigPath": "apps/api/tsconfig.app.json" - }, - "monorepo": true, - "root": "apps/api", - "sourceRoot": "apps/api/src" + } } \ No newline at end of file diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 543aa1f9..db2c7091 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -38,9 +38,9 @@ "bullmq": "^3.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", - "crypto": "^1.0.1", "form-data": "^4.0.0", "ioredis": "^5.3.2", + "ipfs-only-hash": "^4.0.0", "joi": "^17.9.1", "mime-types": "^2.1.35", "multiformats": "^9.9.0", @@ -67,8 +67,12 @@ "eslint-plugin-nestjs": "^1.2.3", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", + "jest": "^29.5.0", "license-report": "^6.4.0", + "prettier": "^3.0.2", + "source-map-support": "^0.5.21", "supertest": "^6.3.3", + "trace-unhandled": "^2.0.1", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", @@ -212,6 +216,11 @@ "node": ">=12.0.0" } }, + "node_modules/@assemblyscript/loader": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", + "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" + }, "node_modules/@babel/code-frame": { "version": "7.22.5", "license": "MIT", @@ -742,8 +751,7 @@ "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@colors/colors": { "version": "1.5.0", @@ -988,7 +996,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.5.0", "@types/node": "*", @@ -1005,7 +1012,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.5.0", "@jest/reporters": "^29.5.0", @@ -1114,7 +1120,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.5.0", @@ -1157,7 +1162,6 @@ "version": "3.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1165,14 +1169,12 @@ "node_modules/@jest/reporters/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -1192,7 +1194,6 @@ "version": "29.4.3", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.15", "callsites": "^3.0.0", @@ -1206,7 +1207,6 @@ "version": "3.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1214,14 +1214,12 @@ "node_modules/@jest/source-map/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -1231,7 +1229,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.5.0", "@jest/types": "^29.5.0", @@ -1246,7 +1243,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.5.0", "graceful-fs": "^4.2.9", @@ -1393,6 +1389,11 @@ "node": ">=8" } }, + "node_modules/@multiformats/base-x": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", + "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" + }, "node_modules/@multiformats/blake2": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/@multiformats/blake2/-/blake2-1.0.13.tgz", @@ -2474,6 +2475,60 @@ "node": ">=16" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@redis/bloom": { "version": "1.2.0", "license": "MIT", @@ -2651,7 +2706,6 @@ "version": "7.20.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -2664,7 +2718,6 @@ "version": "7.6.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -2673,7 +2726,6 @@ "version": "7.4.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -2824,6 +2876,11 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, + "node_modules/@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==" + }, "node_modules/@types/multer": { "version": "1.4.7", "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", @@ -2845,6 +2902,11 @@ "@types/node": "*" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3790,6 +3852,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", @@ -3835,7 +3905,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/transform": "^29.5.0", "@types/babel__core": "^7.1.14", @@ -3870,7 +3939,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -3906,7 +3974,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" @@ -4189,7 +4256,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -4355,6 +4421,30 @@ "node": ">=6" } }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys/node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "engines": { + "node": ">=8" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001502", "funding": [ @@ -4391,7 +4481,6 @@ "version": "1.0.2", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -4457,11 +4546,34 @@ "node": ">=8" } }, + "node_modules/cids": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.9.tgz", + "integrity": "sha512-l11hWRfugIcbGuTZwAM5PwpjPPjyb6UZOGwlHSnOBV5o07XhQ4gNpBN67FbODvpjyHtd+0Xs6KNvUcGBiDRsdg==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "multibase": "^4.0.1", + "multicodec": "^3.0.1", + "multihashes": "^4.0.1", + "uint8arrays": "^3.0.0" + }, + "engines": { + "node": ">=4.0.0", + "npm": ">=3.0.0" + } + }, + "node_modules/cids/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/cjs-module-lexer": { "version": "1.2.3", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/class-transformer": { "version": "0.5.1", @@ -4606,7 +4718,6 @@ "version": "4.6.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -4615,8 +4726,7 @@ "node_modules/collect-v8-coverage": { "version": "1.0.1", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", @@ -4826,12 +4936,6 @@ "node": ">= 8" } }, - "node_modules/crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", - "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." - }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "license": "MIT", @@ -4869,6 +4973,37 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "dev": true, @@ -4897,8 +5032,7 @@ "node_modules/dedent": { "version": "0.7.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/deep-extend": { "version": "0.6.0", @@ -5030,7 +5164,6 @@ "version": "3.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -5119,7 +5252,6 @@ "version": "0.13.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5163,6 +5295,11 @@ "dev": true, "license": "MIT" }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, "node_modules/error-ex": { "version": "1.3.2", "license": "MIT", @@ -5876,7 +6013,6 @@ "node_modules/exit": { "version": "0.1.2", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -6210,6 +6346,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", @@ -6239,8 +6388,7 @@ }, "node_modules/form-data": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -6587,6 +6735,14 @@ "uglify-js": "^3.1.4" } }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "engines": { + "node": ">=6" + } + }, "node_modules/has": { "version": "1.0.3", "license": "MIT", @@ -6665,6 +6821,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/haxec": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/haxec/-/haxec-2.0.1.tgz", + "integrity": "sha512-2DaSqGZIzgVkZ4YFHbk9Su0Q6gm7YbzNX9njOHK/D/XklOdvgTemsPmjcyExlLdkl7lRlNIW0Wxo6niVfpWedw==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "spawn-wrap": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -6682,11 +6851,21 @@ "node": "*" } }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/http-cache-semantics": { "version": "4.1.1", @@ -6781,7 +6960,6 @@ "version": "3.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -6803,6 +6981,14 @@ "node": ">=0.8.19" } }, + "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==", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "license": "ISC", @@ -6863,6 +7049,17 @@ "resolved": "https://registry.npmjs.org/int53/-/int53-0.2.4.tgz", "integrity": "sha512-a5jlKftS7HUOhkUyYD7j2sJ/ZnvWiNlZS1ldR+g1ifQ+/UuZXIE+YTc/lK1qGj/GwAU5F8Z0e1eVq2t1J5Ob2g==" }, + "node_modules/interface-ipld-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/interface-ipld-format/-/interface-ipld-format-1.0.1.tgz", + "integrity": "sha512-WV/ar+KQJVoQpqRDYdo7YPGYIUHJxCuOEhdvsRpzLqoOIVCqPKdMMYmsLL1nCRsF3yYNio+PAJbCKiv6drrEAg==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "cids": "^1.1.6", + "multicodec": "^3.0.1", + "multihashes": "^4.0.2" + } + }, "node_modules/internal-slot": { "version": "1.0.5", "dev": true, @@ -6913,6 +7110,208 @@ "node": ">= 0.10" } }, + "node_modules/ipfs-only-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ipfs-only-hash/-/ipfs-only-hash-4.0.0.tgz", + "integrity": "sha512-TE1DZCvfw8i3gcsTq3P4TFx3cKFJ3sluu/J3XINkJhIN9OwJgNMqKA+WnKx6ByCb1IoPXsTp1KM7tupElb6SyA==", + "dependencies": { + "ipfs-unixfs-importer": "^7.0.1", + "meow": "^9.0.0" + }, + "bin": { + "ipfs-only-hash": "cli.js" + } + }, + "node_modules/ipfs-only-hash/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/ipfs-only-hash/node_modules/hamt-sharding": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-2.0.1.tgz", + "integrity": "sha512-vnjrmdXG9dDs1m/H4iJ6z0JFI2NtgsW5keRkTcM85NGak69Mkf5PHUqBz+Xs0T4sg0ppvj9O5EGAJo40FTxmmA==", + "dependencies": { + "sparse-array": "^1.3.1", + "uint8arrays": "^3.0.0" + }, + "engines": { + "node": ">=10.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/ipfs-only-hash/node_modules/hamt-sharding/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/ipfs-only-hash/node_modules/ipfs-unixfs": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-4.0.3.tgz", + "integrity": "sha512-hzJ3X4vlKT8FQ3Xc4M1szaFVjsc1ZydN+E4VQ91aXxfpjFn9G2wsMo1EFdAXNq/BUnN5dgqIOMP5zRYr3DTsAw==", + "dependencies": { + "err-code": "^3.0.1", + "protobufjs": "^6.10.2" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-only-hash/node_modules/ipfs-unixfs-importer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-7.0.3.tgz", + "integrity": "sha512-qeFOlD3AQtGzr90sr5Tq1Bi8pT5Nr2tSI8z310m7R4JDYgZc6J1PEZO3XZQ8l1kuGoqlAppBZuOYmPEqaHcVQQ==", + "dependencies": { + "bl": "^5.0.0", + "cids": "^1.1.5", + "err-code": "^3.0.1", + "hamt-sharding": "^2.0.0", + "ipfs-unixfs": "^4.0.3", + "ipld-dag-pb": "^0.22.2", + "it-all": "^1.0.5", + "it-batch": "^1.0.8", + "it-first": "^1.0.6", + "it-parallel-batch": "^1.0.9", + "merge-options": "^3.0.4", + "multihashing-async": "^2.1.0", + "rabin-wasm": "^0.1.4", + "uint8arrays": "^2.1.2" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ipfs-only-hash/node_modules/it-all": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", + "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==" + }, + "node_modules/ipfs-only-hash/node_modules/it-batch": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-1.0.9.tgz", + "integrity": "sha512-7Q7HXewMhNFltTsAMdSz6luNhyhkhEtGGbYek/8Xb/GiqYMtwUmopE1ocPSiJKKp3rM4Dt045sNFoUu+KZGNyA==" + }, + "node_modules/ipfs-only-hash/node_modules/it-first": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-1.0.7.tgz", + "integrity": "sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==" + }, + "node_modules/ipfs-only-hash/node_modules/it-parallel-batch": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-1.0.11.tgz", + "integrity": "sha512-UWsWHv/kqBpMRmyZJzlmZeoAMA0F3SZr08FBdbhtbe+MtoEBgr/ZUAKrnenhXCBrsopy76QjRH2K/V8kNdupbQ==", + "dependencies": { + "it-batch": "^1.0.9" + } + }, + "node_modules/ipfs-only-hash/node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/ipfs-only-hash/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==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ipfs-only-hash/node_modules/uint8arrays": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.10.tgz", + "integrity": "sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/ipld-dag-pb": { + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/ipld-dag-pb/-/ipld-dag-pb-0.22.3.tgz", + "integrity": "sha512-dfG5C5OVAR4FEP7Al2CrHWvAyIM7UhAQrjnOYOIxXGQz5NlEj6wGX0XQf6Ru6or1na6upvV3NQfstapQG8X2rg==", + "deprecated": "This module has been superseded by @ipld/dag-pb and multiformats", + "dependencies": { + "cids": "^1.0.0", + "interface-ipld-format": "^1.0.0", + "multicodec": "^3.0.1", + "multihashing-async": "^2.0.0", + "protobufjs": "^6.10.2", + "stable": "^0.1.8", + "uint8arrays": "^2.0.5" + }, + "engines": { + "node": ">=6.0.0", + "npm": ">=3.0.0" + } + }, + "node_modules/ipld-dag-pb/node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, + "node_modules/ipld-dag-pb/node_modules/uint8arrays": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.10.tgz", + "integrity": "sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "dev": true, @@ -7033,7 +7432,6 @@ "version": "2.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7113,6 +7511,14 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.1.4", "dev": true, @@ -7218,6 +7624,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "dev": true, @@ -7291,7 +7706,6 @@ "version": "3.0.0", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", @@ -7305,7 +7719,6 @@ "version": "4.0.1", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -7319,7 +7732,6 @@ "version": "3.1.5", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -7339,7 +7751,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.5.0", "@jest/types": "^29.5.0", @@ -7365,7 +7776,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "execa": "^5.0.0", "p-limit": "^3.1.0" @@ -7378,7 +7788,6 @@ "version": "5.1.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -7401,7 +7810,6 @@ "version": "2.1.0", "dev": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=10.17.0" } @@ -7410,7 +7818,6 @@ "version": "2.0.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -7422,7 +7829,6 @@ "version": "2.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7431,7 +7837,6 @@ "version": "4.0.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -7443,7 +7848,6 @@ "version": "5.1.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -7458,7 +7862,6 @@ "version": "2.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7467,7 +7870,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.5.0", "@jest/expect": "^29.5.0", @@ -7498,7 +7900,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.5.0", "@jest/test-result": "^29.5.0", @@ -7532,7 +7933,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.5.0", @@ -7590,7 +7990,6 @@ "version": "29.4.3", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -7602,7 +8001,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.5.0", "chalk": "^4.0.0", @@ -7618,7 +8016,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.5.0", "@jest/fake-timers": "^29.5.0", @@ -7665,7 +8062,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-get-type": "^29.4.3", "pretty-format": "^29.5.0" @@ -7721,7 +8117,6 @@ "version": "1.2.3", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -7745,7 +8140,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -7765,7 +8159,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-regex-util": "^29.4.3", "jest-snapshot": "^29.5.0" @@ -7778,7 +8171,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.5.0", "@jest/environment": "^29.5.0", @@ -7810,7 +8202,6 @@ "version": "0.5.13", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -7820,7 +8211,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.5.0", "@jest/fake-timers": "^29.5.0", @@ -7853,7 +8243,6 @@ "version": "4.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7909,7 +8298,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.5.0", "camelcase": "^6.2.0", @@ -7926,7 +8314,6 @@ "version": "6.3.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -7938,7 +8325,6 @@ "version": "29.5.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.5.0", "@jest/types": "^29.5.0", @@ -7990,6 +8376,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -8070,11 +8461,18 @@ "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "3.0.3", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -8083,7 +8481,6 @@ "version": "3.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -8262,7 +8659,6 @@ "version": "3.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -8277,7 +8673,6 @@ "version": "6.3.1", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -8294,6 +8689,17 @@ "tmpl": "1.0.5" } }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/media-typer": { "version": "0.3.0", "license": "MIT", @@ -8312,10 +8718,73 @@ "node": ">= 4.0.0" } }, + "node_modules/meow": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz", + "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==", + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize": "^1.2.0", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/meow/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/merge-descriptors": { "version": "1.0.1", "license": "MIT" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-options/node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "license": "MIT" @@ -8365,8 +8834,7 @@ }, "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" }, @@ -8396,6 +8864,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "license": "ISC", @@ -8413,6 +8889,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/minipass": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", @@ -8485,11 +8974,102 @@ "node": ">= 6.0.0" } }, + "node_modules/multibase": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/multibase/-/multibase-4.0.6.tgz", + "integrity": "sha512-x23pDe5+svdLz/k5JPGCVdfn7Q5mZVMBETiC+ORfO+sor9Sgs0smJzAjfTbM5tckeCqnaUuMYoz+k3RXMmJClQ==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "@multiformats/base-x": "^4.0.1" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/multicodec": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/multicodec/-/multicodec-3.2.1.tgz", + "integrity": "sha512-+expTPftro8VAW8kfvcuNNNBgb9gPeNYV9dn+z1kJRWF2vih+/S79f2RVeIwmrJBUJ6NT9IUPWnZDQvegEh5pw==", + "deprecated": "This module has been superseded by the multiformats module", + "dependencies": { + "uint8arrays": "^3.0.0", + "varint": "^6.0.0" + } + }, + "node_modules/multicodec/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/multiformats": { "version": "9.9.0", "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" }, + "node_modules/multihashes": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/multihashes/-/multihashes-4.0.3.tgz", + "integrity": "sha512-0AhMH7Iu95XjDLxIeuCOOE4t9+vQZsACyKZ9Fxw2pcsRmlX4iCn1mby0hS0bb+nQOVpdQYWPpnyusw4da5RPhA==", + "dependencies": { + "multibase": "^4.0.1", + "uint8arrays": "^3.0.0", + "varint": "^5.0.2" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/multihashes/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/multihashes/node_modules/varint": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz", + "integrity": "sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==" + }, + "node_modules/multihashing-async": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/multihashing-async/-/multihashing-async-2.1.4.tgz", + "integrity": "sha512-sB1MiQXPSBTNRVSJc2zM157PXgDtud2nMFUEIvBrsq5Wv96sUclMRK/ecjoP1T/W61UJBqt4tCTwMkUpt2Gbzg==", + "dependencies": { + "blakejs": "^1.1.0", + "err-code": "^3.0.0", + "js-sha3": "^0.8.0", + "multihashes": "^4.0.1", + "murmurhash3js-revisited": "^3.0.0", + "uint8arrays": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/multihashing-async/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" + } + }, + "node_modules/murmurhash3js-revisited": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/murmurhash3js-revisited/-/murmurhash3js-revisited-3.0.0.tgz", + "integrity": "sha512-/sF3ee6zvScXMb1XFJ8gDsSnY+X8PbOyjIuBhtgis10W2Jx4ZjIhikUCIF9c4gpJxVnQIsPAFrSwTCuAjicP6g==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -8612,6 +9192,20 @@ "version": "2.0.12", "license": "MIT" }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "license": "MIT", @@ -9023,7 +9617,6 @@ "version": "4.2.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -9035,7 +9628,6 @@ "version": "4.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -9048,7 +9640,6 @@ "version": "5.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -9060,7 +9651,6 @@ "version": "2.3.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -9075,7 +9665,6 @@ "version": "4.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -9104,7 +9693,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", "dev": true, - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -9156,7 +9744,6 @@ "version": "2.4.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -9216,8 +9803,7 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/q": { "version": "1.5.1", @@ -9271,6 +9857,64 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/rabin-wasm": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/rabin-wasm/-/rabin-wasm-0.1.5.tgz", + "integrity": "sha512-uWgQTo7pim1Rnj5TuWcCewRDTf0PEFTSlaUjWP4eY9EbLV9em08v89oCz/WO+wRxpYuO36XEHp4wgYQnAgOHzA==", + "dependencies": { + "@assemblyscript/loader": "^0.9.4", + "bl": "^5.0.0", + "debug": "^4.3.1", + "minimist": "^1.2.5", + "node-fetch": "^2.6.1", + "readable-stream": "^3.6.0" + }, + "bin": { + "rabin-wasm": "cli/bin.js" + } + }, + "node_modules/rabin-wasm/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/rabin-wasm/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/rabin-wasm/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==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -9325,6 +9969,124 @@ "version": "18.2.0", "license": "MIT" }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/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==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/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==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/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==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/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==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "engines": { + "node": ">=8" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "license": "MIT", @@ -9363,6 +10125,18 @@ "node": ">= 0.10" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/redis": { "version": "4.6.7", "license": "MIT", @@ -9481,7 +10255,6 @@ "version": "3.0.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -9493,7 +10266,6 @@ "version": "5.0.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -9517,7 +10289,6 @@ "version": "2.0.2", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -9928,8 +10699,7 @@ "node_modules/sisteransi": { "version": "1.0.5", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", @@ -9967,6 +10737,56 @@ "source-map": "^0.6.0" } }, + "node_modules/sparse-array": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", + "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==" + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "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==", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==" + }, "node_modules/split-text-to-chunks": { "version": "1.0.0", "dev": true, @@ -9983,6 +10803,12 @@ "version": "1.0.3", "license": "BSD-3-Clause" }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" + }, "node_modules/stack-utils": { "version": "2.0.6", "license": "MIT", @@ -10032,7 +10858,6 @@ "version": "4.0.2", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -10123,6 +10948,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "dev": true, @@ -10460,6 +11296,21 @@ "version": "0.0.3", "license": "MIT" }, + "node_modules/trace-unhandled": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/trace-unhandled/-/trace-unhandled-2.0.1.tgz", + "integrity": "sha512-wOZbhBiNyuZTs0b/ADZFTiTDVVDsvKQj/RkVJTKefH6u9CowGDSR+H/3miaGUrYCCuzS0nVmIzpbIIm6lRF8gg==", + "dev": true, + "dependencies": { + "haxec": "^2.0.1" + }, + "bin": { + "trace-unhandled": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tree-kill": { "version": "1.2.2", "license": "MIT", @@ -10467,6 +11318,14 @@ "tree-kill": "cli.js" } }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "engines": { + "node": ">=8" + } + }, "node_modules/ts-jest": { "version": "29.1.0", "dev": true, @@ -11047,7 +11906,6 @@ "version": "9.1.0", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -11061,7 +11919,6 @@ "version": "3.1.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -11069,14 +11926,12 @@ "node_modules/v8-to-istanbul/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -11085,8 +11940,16 @@ "node_modules/v8-to-istanbul/node_modules/convert-source-map": { "version": "1.9.0", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" + }, + "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==", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } }, "node_modules/validator": { "version": "13.9.0", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 46b470d1..9855c779 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -4,10 +4,15 @@ "description": "Services to publish content on DSNP/Frequency", "main": "dist/apps/api/main.js", "scripts": { - "build": "npx tsc", + "build": "nest build", "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", - "start": "env TS_NODE_BASEURL=./dist/app/api node -r tsconfig-paths/register dist/apps/api/main.js", - "start:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", + "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", + "start": "nest start", + "start:api": "nest start api", + "start:worker": "nest start worker", + "start:api:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", + "start:worker:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register apps/worker/src/main.ts", + "start:debug": "nest start --debug --watch", "start:dev:docker": "npm ci && ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", "docker-build": "docker build -t content-publishing-service .", "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", @@ -15,7 +20,7 @@ "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs -f content-publishing-service", "docker-stop:dev": "docker-compose -f docker-compose.dev.yaml stop", "clean": "rm -Rf dist", - "lint": "tsc --noEmit --pretty && eslint \"**/*.ts\" --fix", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "pretest": "cp env.template .env", "test": "jest --coverage --verbose", "test:e2e": "jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles" @@ -61,9 +66,9 @@ "bullmq": "^3.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", - "crypto": "^1.0.1", "form-data": "^4.0.0", "ioredis": "^5.3.2", + "ipfs-only-hash": "^4.0.0", "joi": "^17.9.1", "mime-types": "^2.1.35", "multiformats": "^9.9.0", @@ -90,8 +95,12 @@ "eslint-plugin-nestjs": "^1.2.3", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", + "jest": "^29.5.0", "license-report": "^6.4.0", + "prettier": "^3.0.2", + "source-map-support": "^0.5.21", "supertest": "^6.3.3", + "trace-unhandled": "^2.0.1", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", diff --git a/services/content-watcher/tsconfig.build.json b/services/content-watcher/tsconfig.build.json new file mode 100644 index 00000000..c460c168 --- /dev/null +++ b/services/content-watcher/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "./**/*.ts" + ], + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/services/content-watcher/tsconfig.json b/services/content-watcher/tsconfig.json index 4f5f22b5..8b70d256 100644 --- a/services/content-watcher/tsconfig.json +++ b/services/content-watcher/tsconfig.json @@ -1,6 +1,4 @@ { - "$schema": "https://json.schemastore.org/tsconfig", - "display": "Base", "compilerOptions": { "allowSyntheticDefaultImports": true, "baseUrl": "./src", @@ -28,17 +26,9 @@ "strict": true, "skipLibCheck": true, "strictPropertyInitialization": false, - "target": "es2022", + "target": "es2021", "typeRoots": [ "node_modules/@types" ] - }, - "include": [ - "./**/*.ts" - ], - "exclude": [ - "node_modules/**", - "./dist/**", - "/tools/**" - ] + } } \ No newline at end of file From 682ab722b3ba3f5d98885fc36f169567d9445736 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Wed, 6 Sep 2023 18:13:58 -0500 Subject: [PATCH 013/137] IPFS announcer: announces batches on ipfs as parquet file (#28) * add announcements to payload * park some work * add parquet placeholders * some placeholders * add more logic for IPFS announcement * cleanup * add depdencies * remove helia * setup helia * breaking * comment our multiformats for now * set pinning note for announcer * cleanup * cleanup: todo, multiformats issue * use multiformats 0.9.9 * placeholders * set more placeholders * base 32 * cleanup * fill in the blanks * adda test * cleanup batchAnnouncer/extract dnspConverter * revert * cleanup * cleanup * address feedback * cleanupand fix tests * revert * use cache for schemas * rename --- .../api/src/config/config.service.spec.ts | 4 + .../apps/api/src/config/env.config.ts | 4 + .../batch_announcer/batch.announcer.module.ts | 9 +- .../batch.announcer.service.ts | 10 +- .../batch_announcer/batch.announcer.spec.ts | 88 +++++++++ .../src/batch_announcer/batch.announcer.ts | 89 +++++++++ .../batch_announcer/ipfs.announcer.spec.ts | 13 -- .../src/batch_announcer/ipfs.announcer.ts | 16 -- .../src/blockchain/blockchain.service.ts | 9 +- .../batch-announcer.job.interface.ts | 3 + services/content-watcher/env.template | 4 + .../libs/common/src/interfaces/dsnp.ts | 183 ++++++++++++++++++ .../common/src/utils/dsnpTypeConverter.ts | 176 +++++++++++++++++ .../libs/common/src/utils/ipfs.client.ts | 2 +- services/content-watcher/package-lock.json | 142 ++++++++++++-- services/content-watcher/package.json | 5 +- 16 files changed, 700 insertions(+), 57 deletions(-) create mode 100644 services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts create mode 100644 services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts delete mode 100644 services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.spec.ts delete mode 100644 services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts create mode 100644 services/content-watcher/libs/common/src/interfaces/dsnp.ts create mode 100644 services/content-watcher/libs/common/src/utils/dsnpTypeConverter.ts diff --git a/services/content-watcher/apps/api/src/config/config.service.spec.ts b/services/content-watcher/apps/api/src/config/config.service.spec.ts index 52bdd194..549c54c9 100644 --- a/services/content-watcher/apps/api/src/config/config.service.spec.ts +++ b/services/content-watcher/apps/api/src/config/config.service.spec.ts @@ -38,6 +38,10 @@ describe('ContentPublishingConfigService', () => { const ALL_ENV: { [key: string]: string | undefined } = { REDIS_URL: undefined, FREQUENCY_URL: undefined, + IPFS_ENDPOINT: undefined, + IPFS_GATEWAY_URL: undefined, + IPFS_BASIC_AUTH_USER: undefined, + IPFS_BASIC_AUTH_SECRET: undefined, PROVIDER_ID: undefined, BLOCKCHAIN_SCAN_INTERVAL_MINUTES: undefined, QUEUE_HIGH_WATER: undefined, diff --git a/services/content-watcher/apps/api/src/config/env.config.ts b/services/content-watcher/apps/api/src/config/env.config.ts index 8e83ddf8..a6bd6411 100644 --- a/services/content-watcher/apps/api/src/config/env.config.ts +++ b/services/content-watcher/apps/api/src/config/env.config.ts @@ -5,6 +5,10 @@ import { mnemonicValidate } from '@polkadot/util-crypto'; export const configModuleOptions: ConfigModuleOptions = { isGlobal: true, validationSchema: Joi.object({ + IPFS_ENDPOINT: Joi.string().uri().required(), + IPFS_GATEWAY_URL: Joi.string().required(), // This is parse as string as the required format of this not a valid uri, check .env.template + IPFS_BASIC_AUTH_USER: Joi.string().allow('').default(''), + IPFS_BASIC_AUTH_SECRET: Joi.string().allow('').default(''), REDIS_URL: Joi.string().uri().required(), FREQUENCY_URL: Joi.string().uri().required(), PROVIDER_ID: Joi.required().custom((value: string, helpers) => { diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts index 206e1284..6c2c1f84 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts @@ -9,12 +9,15 @@ import { RedisModule } from '@liaoliaots/nestjs-redis'; import { BatchAnnouncementService } from './batch.announcer.service'; import { ConfigModule } from '../../../api/src/config/config.module'; import { ConfigService } from '../../../api/src/config/config.service'; -import { IPFSAnnouncer } from './ipfs.announcer'; +import { BatchAnnouncer } from './batch.announcer'; import { QueueConstants } from '../../../../libs/common/src'; +import { BlockchainModule } from '../blockchain/blockchain.module'; +import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; @Module({ imports: [ ConfigModule, + BlockchainModule, EventEmitterModule, RedisModule.forRootAsync( { @@ -75,7 +78,7 @@ import { QueueConstants } from '../../../../libs/common/src'; ), ], controllers: [], - providers: [BatchAnnouncementService, IPFSAnnouncer], - exports: [BullModule, BatchAnnouncementService, IPFSAnnouncer], + providers: [BatchAnnouncementService, BatchAnnouncer, IpfsService], + exports: [BullModule, BatchAnnouncementService, BatchAnnouncer, IpfsService], }) export class BatchAnnouncerModule {} diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts index 9a9ce51b..0bb623d2 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts @@ -6,7 +6,7 @@ import Redis from 'ioredis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { ConfigService } from '../../../api/src/config/config.service'; -import { IPFSAnnouncer } from './ipfs.announcer'; +import { BatchAnnouncer } from './batch.announcer'; import { CAPACITY_EPOCH_TIMEOUT_NAME } from '../../../../libs/common/src/constants'; import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; import { QueueConstants } from '../../../../libs/common/src'; @@ -18,13 +18,11 @@ import { QueueConstants } from '../../../../libs/common/src'; export class BatchAnnouncementService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { private logger: Logger; - private capacityExhausted = false; - constructor( @InjectRedis() private cacheManager: Redis, @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, private configService: ConfigService, - private ipfsPublisher: IPFSAnnouncer, + private ipfsPublisher: BatchAnnouncer, private schedulerRegistry: SchedulerRegistry, private eventEmitter: EventEmitter2, ) { @@ -47,7 +45,9 @@ export class BatchAnnouncementService extends WorkerHost implements OnApplicatio async process(job: Job): Promise { this.logger.log(`Processing job ${job.id} of type ${job.name}`); try { - await this.ipfsPublisher.announce(job.data); + const publisherJob = await this.ipfsPublisher.announce(job.data); + + await this.publishQueue.add(publisherJob.id, publisherJob); this.logger.log(`Completed job ${job.id} of type ${job.name}`); return job.data; } catch (e) { diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts new file mode 100644 index 00000000..52ffefdc --- /dev/null +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts @@ -0,0 +1,88 @@ +import { expect, describe, jest, it, beforeEach } from '@jest/globals'; +import assert from 'assert'; +import { FrequencyParquetSchema } from '@dsnp/frequency-schemas/types/frequency'; +import Redis from 'ioredis-mock'; +import { BatchAnnouncer } from './batch.announcer'; + +// Create a mock for the dependencies +const mockConfigService = { + getIpfsCidPlaceholder: jest.fn(), +}; + +const mockBlockchainService = { + getSchema: jest.fn(), +}; + +const mockIpfsService = { + getPinned: jest.fn(), + ipfsPin: jest.fn(), +}; + +describe('BatchAnnouncer', () => { + let ipfsAnnouncer: BatchAnnouncer; + + const broadcast: FrequencyParquetSchema = [ + { + name: 'announcementType', + column_type: { + INTEGER: { + bit_width: 32, + sign: true, + }, + }, + compression: 'GZIP', + bloom_filter: false, + }, + { + name: 'contentHash', + column_type: 'BYTE_ARRAY', + compression: 'GZIP', + bloom_filter: true, + }, + { + name: 'fromId', + column_type: { + INTEGER: { + bit_width: 64, + sign: false, + }, + }, + compression: 'GZIP', + bloom_filter: true, + }, + { + name: 'url', + column_type: 'STRING', + compression: 'GZIP', + bloom_filter: false, + }, + ]; + const mockClient = new Redis(); + + beforeEach(async () => { + ipfsAnnouncer = new BatchAnnouncer(mockClient, mockConfigService as any, mockBlockchainService as any, mockIpfsService as any); + }); + it('should be defined', () => { + expect(ipfsAnnouncer).toBeDefined(); + }); + + // Write your test cases here + it('should announce a batch to IPFS', async () => { + // Mock the necessary dependencies' behavior + mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); + mockBlockchainService.getSchema.mockReturnValue({ model: JSON.stringify(broadcast) }); + mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); + mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', size: 'mockSize' }); + + const batchJob = { + batchId: 'mockBatchId', + schemaId: 123, + announcements: [], + }; + + const result = await ipfsAnnouncer.announce(batchJob); + assert(result); + expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); + expect(mockBlockchainService.getSchema).toHaveBeenCalledWith(123); + }); +}); diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts new file mode 100644 index 00000000..1f39cedf --- /dev/null +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts @@ -0,0 +1,89 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PassThrough } from 'node:stream'; +import { ParquetWriter } from '@dsnp/parquetjs'; +import { fromFrequencySchema } from '@dsnp/frequency-schemas/parquet'; +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import Redis from 'ioredis'; +import { PalletSchemasSchema } from '@polkadot/types/lookup'; +import { BlockchainService } from '../blockchain/blockchain.service'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; +import { IPublisherJob } from '../interfaces/publisher-job.interface'; +import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; + +@Injectable() +export class BatchAnnouncer { + private logger: Logger; + + constructor( + @InjectRedis() private cacheManager: Redis, + private configService: ConfigService, + private blockchainService: BlockchainService, + private ipfsService: IpfsService, + ) { + this.logger = new Logger(BatchAnnouncer.name); + } + + public async announce(batchJob: IBatchAnnouncerJobData): Promise { + this.logger.debug(`Announcing batch ${batchJob.batchId} on IPFS`); + const { batchId, schemaId, announcements } = batchJob; + + let frequencySchema: PalletSchemasSchema; + + const schemaCacheKey = `schema:${schemaId}`; + const cachedSchema = await this.cacheManager.get(schemaCacheKey); + if (cachedSchema) { + frequencySchema = JSON.parse(cachedSchema); + } else { + frequencySchema = await this.blockchainService.getSchema(schemaId); + await this.cacheManager.set(schemaCacheKey, JSON.stringify(frequencySchema)); + } + + const schema = JSON.parse(frequencySchema.model.toString()); + if (!schema) { + throw new Error(`Unable to parse schema for schemaId ${schemaId}`); + } + + const [parquetSchema, writerOptions] = fromFrequencySchema(schema); + const publishStream = new PassThrough(); + + const writer = await ParquetWriter.openStream(parquetSchema, publishStream as any, writerOptions); + + announcements.forEach(async (announcement) => { + writer.appendRow(announcement); + }); + + await writer.close(); + const buffer = await this.bufferPublishStream(publishStream); + const [cid, hash] = await this.pinStringToIPFS(buffer); + const ipfsUrl = await this.formIpfsUrl(cid); + this.logger.debug(`Batch ${batchId} published to IPFS at ${ipfsUrl}`); + this.logger.debug(`Batch ${batchId} hash: ${hash}`); + return { id: batchId, schemaId, data: { cid, payloadLength: buffer.length } }; + } + + private async bufferPublishStream(publishStream: PassThrough): Promise { + this.logger.debug('Buffering publish stream'); + return new Promise((resolve, reject) => { + const buffers: Buffer[] = []; + publishStream.on('data', (data) => { + buffers.push(data); + }); + publishStream.on('end', () => { + resolve(Buffer.concat(buffers)); + }); + publishStream.on('error', (err) => { + reject(err); + }); + }); + } + + private async pinStringToIPFS(buf: Buffer): Promise<[string, string]> { + const { cid, size } = await this.ipfsService.ipfsPin('application/octet-stream', buf); + return [cid.toString(), size.toString()]; + } + + private async formIpfsUrl(cid: string): Promise { + return this.configService.getIpfsCidPlaceholder(cid); + } +} diff --git a/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.spec.ts b/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.spec.ts deleted file mode 100644 index fafa3c61..00000000 --- a/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -// test file for ipfs announcer -import { describe, it, beforeEach } from '@jest/globals'; -import { IPFSAnnouncer } from './ipfs.announcer'; - -describe('IPFSAnnouncer', () => { - let ipfsAnnouncer: IPFSAnnouncer; - - beforeEach(async () => {}); - - describe('announce', () => { - it('should announce a batch on ipfs', async () => {}); - }); -}); diff --git a/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts deleted file mode 100644 index f548271e..00000000 --- a/services/content-watcher/apps/worker/src/batch_announcer/ipfs.announcer.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { ConfigService } from '../../../api/src/config/config.service'; -import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; - -@Injectable() -export class IPFSAnnouncer { - private logger: Logger; - - constructor(private configService: ConfigService) { - this.logger = new Logger(IPFSAnnouncer.name); - } - - public async announce(batchJob: IBatchAnnouncerJobData): Promise { - this.logger.log(`Announcing batch ${batchJob.batchId} on IPFS`); - } -} diff --git a/services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts b/services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts index 3cdd832d..00b303d2 100644 --- a/services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts @@ -7,8 +7,8 @@ import { KeyringPair } from '@polkadot/keyring/types'; import { BlockHash, BlockNumber } from '@polkadot/types/interfaces'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; -import { u32, Option, u128 } from '@polkadot/types'; -import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo } from '@polkadot/types/lookup'; +import { u32, Option } from '@polkadot/types'; +import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo, PalletSchemasSchema } from '@polkadot/types/lookup'; import { ConfigService } from '../../../api/src/config/config.service'; import { Extrinsic } from './extrinsic'; @@ -139,4 +139,9 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS public async capacityBatchLimit(): Promise { return this.api.consts.frequencyTxPayment.maximumCapacityBatchLength.toNumber(); } + + public async getSchema(schemaId: number): Promise { + const schema: PalletSchemasSchema = await this.query('schemas', 'schemas', schemaId); + return schema; + } } diff --git a/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts b/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts index 25d5bbf9..10cb3b07 100644 --- a/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts +++ b/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts @@ -1,4 +1,7 @@ +import { Announcement } from '../../../../libs/common/src/interfaces/dsnp'; + export interface IBatchAnnouncerJobData { batchId: string; schemaId: number; + announcements: Announcement[]; } diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index 8afa0581..bc6fd8a1 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -1,4 +1,8 @@ # Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development +IPFS_ENDPOINT="https://ipfs.infura.io:5001" +IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" +IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" +IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" FREQUENCY_URL=ws://0.0.0.0:9944 PROVIDER_ID=1 REDIS_URL=redis://0.0.0.0:6379 diff --git a/services/content-watcher/libs/common/src/interfaces/dsnp.ts b/services/content-watcher/libs/common/src/interfaces/dsnp.ts new file mode 100644 index 00000000..5b0170d8 --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/dsnp.ts @@ -0,0 +1,183 @@ +/** + * AnnouncementType: an enum representing different types of DSNP announcements + */ + +import { ActivityContentNote } from '@dsnp/activity-content/types'; + +// eslint-disable-next-line no-shadow +export enum AnnouncementType { + Tombstone = 0, + Broadcast = 2, + Reply = 3, + Reaction = 4, + Profile = 5, + Update = 6, + PublicFollows = 113, +} + +type TombstoneFields = { + announcementType: AnnouncementType.Tombstone; + targetAnnouncementType: AnnouncementType; + targetSignature: string; +}; + +type BroadcastFields = { + announcementType: AnnouncementType.Broadcast; + contentHash: string; + url: string; +}; + +type ReplyFields = { + announcementType: AnnouncementType.Reply; + contentHash: string; + inReplyTo: string; + url: string; +}; + +type ReactionFields = { + announcementType: AnnouncementType.Reaction; + emoji: string; + inReplyTo: string; +}; + +type ProfileFields = { + announcementType: AnnouncementType.Profile; + contentHash: string; + url: string; +}; + +/** + * TypedAnnouncement: an Announcement with a particular AnnouncementType + */ +export type TypedAnnouncement = { + announcementType: T; + fromId: string; +} & (TombstoneFields | BroadcastFields | ReplyFields | ReactionFields | ProfileFields); + +/** + * Announcement: an Announcement intended for inclusion in a batch file + */ +export type Announcement = TypedAnnouncement; + +/** + * ProfileAnnouncement: an Announcement of type Profile + */ +export type ProfileAnnouncement = TypedAnnouncement; + +/** + * TombstoneAnnouncement: an Announcement of type Tombstone + */ +export type TombstoneAnnouncement = TypedAnnouncement; + +/** + * BroadcastAnnouncement: an Announcement of type Broadcast + */ +export type BroadcastAnnouncement = TypedAnnouncement; + +/** + * ReplyAnnouncement: am announcement of type Reply + */ +export type ReplyAnnouncement = TypedAnnouncement; + +/** + * ReactionAnnouncement: an Announcement of type Reaction + */ +export type ReactionAnnouncement = TypedAnnouncement; + +/** + * createTombstone() generates a tombstone announcement from a given URL and + * hash. + * + * @param fromId - The id of the user from whom the announcement is posted + * @param targetType - The DSNP announcement type of the target announcement + * @param targetSignature - The signature of the target announcement + * @returns A TombstoneAnnouncement + */ +export const createTombstone = (fromId: string, targetType: AnnouncementType, targetSignature: string): TombstoneAnnouncement => ({ + announcementType: AnnouncementType.Tombstone, + targetAnnouncementType: targetType, + targetSignature, + fromId, +}); + +/** + * createBroadcast() generates a broadcast announcement from a given URL and + * hash. + * + * @param fromId - The id of the user from whom the announcement is posted + * @param url - The URL of the activity content to reference + * @param hash - The hash of the content at the URL + * @returns A BroadcastAnnouncement + */ +export const createBroadcast = (fromId: string, url: string, hash: string): BroadcastAnnouncement => ({ + announcementType: AnnouncementType.Broadcast, + contentHash: hash, + fromId, + url, +}); + +/** + * createReply() generates a reply announcement from a given URL, hash and + * content uri. + * + * @param fromId - The id of the user from whom the announcement is posted + * @param url - The URL of the activity content to reference + * @param hash - The hash of the content at the URL + * @param inReplyTo - The DSNP Content Uri of the parent announcement + * @returns A ReplyAnnouncement + */ +export const createReply = (fromId: string, url: string, hash: string, inReplyTo: string): ReplyAnnouncement => ({ + announcementType: AnnouncementType.Reply, + contentHash: hash, + fromId, + inReplyTo, + url, +}); + +/** + * createReaction() generates a reaction announcement from a given URL, hash and + * content uri. + * + * @param fromId - The id of the user from whom the announcement is posted + * @param emoji - The emoji to respond with + * @param inReplyTo - The DSNP Content Uri of the parent announcement + * @returns A ReactionAnnouncement + */ +export const createReaction = (fromId: string, emoji: string, inReplyTo: string): ReactionAnnouncement => ({ + announcementType: AnnouncementType.Reaction, + emoji, + fromId, + inReplyTo, +}); + +/** + * createProfile() generates a profile announcement from a given URL and hash. + * + * @param fromId - The id of the user from whom the announcement is posted + * @param url - The URL of the activity content to reference + * @param hash - The hash of the content at the URL + * @returns A ProfileAnnouncement + */ +export const createProfile = (fromId: string, url: string, hash: string): ProfileAnnouncement => ({ + announcementType: AnnouncementType.Profile, + contentHash: hash, + fromId, + url, +}); + +/** + * createNote() provides a simple factory for generating an ActivityContentNote + * object. + * @param content - The text content to include in the note + * @param published - the Date that the note was claimed to be published + * @param options - Overrides default fields for the ActivityContentNote + * @returns An ActivityContentNote object + */ +export const createNote = (content: string, published: Date, options?: Partial): ActivityContentNote => ({ + '@context': 'https://www.w3.org/ns/activitystreams', + type: 'Note', + mediaType: 'text/plain', + published: published.toISOString(), + content, + ...options, +}); diff --git a/services/content-watcher/libs/common/src/utils/dsnpTypeConverter.ts b/services/content-watcher/libs/common/src/utils/dsnpTypeConverter.ts new file mode 100644 index 00000000..014c70dd --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/dsnpTypeConverter.ts @@ -0,0 +1,176 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { + ActivityContentTag, + ActivityContentAttachment, + ActivityContentLink, + ActivityContentImageLink, + ActivityContentImage, + ActivityContentVideoLink, + ActivityContentVideo, + ActivityContentAudioLink, + ActivityContentAudio, +} from '@dsnp/activity-content/types'; +import { TagTypeDto, AssetDto, AttachmentTypeDto } from '../dtos/activity.dto'; +import { createNote } from '../interfaces/dsnp'; +import { calculateDsnpHash } from './ipfs'; +import { IpfsService } from './ipfs.client'; +import { ConfigService } from '../../../../apps/api/src/config/config.service'; + +@Injectable() +export class BatchAnnouncer { + private logger: Logger; + + constructor( + private configService: ConfigService, + private ipfsService: IpfsService, + ) { + this.logger = new Logger(BatchAnnouncer.name); + } + + public async prepareNote(noteContent?: any): Promise<[string, string, string]> { + this.logger.debug(`Preparing note`); + const tags: ActivityContentTag[] = []; + if (noteContent?.content.tag) { + noteContent.content.tag.forEach((tag) => { + switch (tag.type) { + case TagTypeDto.Hashtag: + tags.push({ name: tag.name || '' }); + break; + case TagTypeDto.Mention: + tags.push({ + name: tag.name || '', + type: 'Mention', + id: tag.mentionedId || '', + }); + break; + default: + throw new Error(`Unsupported tag type ${typeof tag.type}`); + } + }); + } + + const attachments: ActivityContentAttachment[] = []; + if (noteContent?.content.assets) { + noteContent.content.assets.forEach(async (asset: AssetDto) => { + switch (asset.type) { + case AttachmentTypeDto.LINK: { + const link: ActivityContentLink = { + type: 'Link', + href: asset.href || '', + name: asset.name || '', + }; + + attachments.push(link); + break; + } + case AttachmentTypeDto.IMAGE: { + const imageLinks: ActivityContentImageLink[] = []; + asset.references?.forEach(async (reference) => { + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + const image: ActivityContentImageLink = { + mediaType: 'image', // TODO + hash: [hashedContent], + height: reference.height, + width: reference.width, + type: 'Link', + href: await this.formIpfsUrl(reference.referenceId), + }; + imageLinks.push(image); + }); + const imageActivity: ActivityContentImage = { + type: 'Image', + name: asset.name || '', + url: imageLinks, + }; + + attachments.push(imageActivity); + break; + } + case AttachmentTypeDto.VIDEO: { + const videoLinks: ActivityContentVideoLink[] = []; + let duration = ''; + asset.references?.forEach(async (reference) => { + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + const video: ActivityContentVideoLink = { + mediaType: 'video', // TODO + hash: [hashedContent], + height: reference.height, + width: reference.width, + type: 'Link', + href: await this.formIpfsUrl(reference.referenceId), + }; + duration = reference.duration ?? ''; + videoLinks.push(video); + }); + const videoActivity: ActivityContentVideo = { + type: 'Video', + name: asset.name || '', + url: videoLinks, + duration, + }; + + attachments.push(videoActivity); + break; + } + case AttachmentTypeDto.AUDIO: { + const audioLinks: ActivityContentAudioLink[] = []; + let duration = ''; + asset.references?.forEach(async (reference) => { + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + duration = reference.duration ?? ''; + const audio: ActivityContentAudioLink = { + mediaType: 'audio', // TODO + hash: [hashedContent], + type: 'Link', + href: await this.formIpfsUrl(reference.referenceId), + }; + audioLinks.push(audio); + }); + const audioActivity: ActivityContentAudio = { + type: 'Audio', + name: asset.name || '', + url: audioLinks, + duration, + }; + + attachments.push(audioActivity); + break; + } + default: + throw new Error(`Unsupported attachment type ${typeof asset.type}`); + } + }); + } + + const note = createNote(noteContent?.content.content ?? '', new Date(noteContent?.content.published ?? ''), { + name: noteContent?.content.name, + location: { + latitude: noteContent?.content.location?.latitude, + longitude: noteContent?.content.location?.longitude, + radius: noteContent?.content.location?.radius, + altitude: noteContent?.content.location?.altitude, + accuracy: noteContent?.content.location?.accuracy, + name: noteContent?.content.location?.name || '', + type: 'Place', + }, + tag: tags, + attachment: attachments, + }); + const noteString = JSON.stringify(note); + const [cid, hash] = await this.pinStringToIPFS(Buffer.from(noteString)); + const ipfsUrl = await this.formIpfsUrl(cid); + return [cid, hash, ipfsUrl]; + } + + private async pinStringToIPFS(buf: Buffer): Promise<[string, string]> { + const { cid, size } = await this.ipfsService.ipfsPin('application/octet-stream', buf); + return [cid.toString(), size.toString()]; + } + + private async formIpfsUrl(cid: string): Promise { + return this.configService.getIpfsCidPlaceholder(cid); + } +} diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index 0729d642..7cc9c320 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -6,9 +6,9 @@ import FormData from 'form-data'; import { extension as getExtension } from 'mime-types'; import { CID } from 'multiformats/cid'; import { blake2b256 as hasher } from '@multiformats/blake2/blake2b'; -import { base58btc } from 'multiformats/bases/base58'; import { create } from 'multiformats/hashes/digest'; import { randomUUID } from 'crypto'; +import { base58btc } from 'multiformats/bases/base58'; import { ConfigService } from '../../../../apps/api/src/config/config.service'; export interface FilePin { diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index db2c7091..67a5f2f3 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -9,6 +9,8 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { + "@dsnp/activity-content": "^1.1.0", + "@dsnp/frequency-schemas": "^1.0.2", "@dsnp/parquetjs": "^1.3.4", "@frequency-chain/api-augment": "1.7.0", "@jest/globals": "^29.5.0", @@ -43,7 +45,7 @@ "ipfs-only-hash": "^4.0.0", "joi": "^17.9.1", "mime-types": "^2.1.35", - "multiformats": "^9.9.0", + "multiformats": "9.9.0", "rxjs": "^7.8.1", "time-constants": "^1.0.3" }, @@ -67,6 +69,7 @@ "eslint-plugin-nestjs": "^1.2.3", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", + "ioredis-mock": "^8.8.3", "jest": "^29.5.0", "license-report": "^6.4.0", "prettier": "^3.0.2", @@ -764,7 +767,6 @@ }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -773,6 +775,49 @@ "node": ">=12" } }, + "node_modules/@dsnp/activity-content": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@dsnp/activity-content/-/activity-content-1.1.0.tgz", + "integrity": "sha512-T83Bi3Nn4uUOySAECaUuNZ+1XkL1c3VENAxKv1m9n76J0GApeLcRfujtWZnPNJGZJp+wgLbmjoTBxulwjbDW4w==", + "dependencies": { + "@multiformats/blake2": "^1.0.13", + "multiformats": "^11.0.2" + } + }, + "node_modules/@dsnp/activity-content/node_modules/multiformats": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-11.0.2.tgz", + "integrity": "sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==", + "engines": { + "node": ">=16.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@dsnp/frequency-schemas": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@dsnp/frequency-schemas/-/frequency-schemas-1.0.2.tgz", + "integrity": "sha512-+u2Fwv9aYbMn7MI5LbiDn92dWK+YxcJJEwdy6r/wdQwFVz/jZtE5lR56KqPYS2piun/vINMJU+HNZUVYL4zkOg==", + "dependencies": { + "@frequency-chain/api-augment": "0.0.0-45e306", + "@polkadot/api": "^10.7.3", + "json-stringify-pretty-compact": "^4.0.0", + "ts-node": "^10.9.1", + "typescript": "^5.0.4" + }, + "optionalDependencies": { + "@dsnp/parquetjs": "^1.3.0" + } + }, + "node_modules/@dsnp/frequency-schemas/node_modules/@frequency-chain/api-augment": { + "version": "0.0.0-45e306", + "resolved": "https://registry.npmjs.org/@frequency-chain/api-augment/-/api-augment-0.0.0-45e306.tgz", + "integrity": "sha512-VzZIFwMX8LaY6dJfzsJ6t9WKZ28DzGA4+lEKys0OR6O+CIAV/fVWNb8FaHG9c6It0GmSz2Os0ehvn5p/rgWfug==", + "dependencies": { + "@polkadot/api": "^10.7.3", + "@polkadot/rpc-provider": "^10.7.3", + "@polkadot/types": "^10.7.3" + } + }, "node_modules/@dsnp/parquetjs": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@dsnp/parquetjs/-/parquetjs-1.3.4.tgz", @@ -898,6 +943,12 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@ioredis/as-callback": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@ioredis/as-callback/-/as-callback-3.0.0.tgz", + "integrity": "sha512-Kqv1rZ3WbgOrS+hgzJ5xG5WQuhvzzSTRYvNeyPMLOAM78MHSnuKI20JeJGbpuAt//LCuP0vsexZcorqW7kWhJg==", + "dev": true + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "license": "MIT" @@ -2684,22 +2735,18 @@ }, "node_modules/@tsconfig/node10": { "version": "1.0.9", - "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", - "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", - "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", - "devOptional": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -2830,6 +2877,16 @@ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" }, + "node_modules/@types/ioredis-mock": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/ioredis-mock/-/ioredis-mock-8.2.2.tgz", + "integrity": "sha512-bnbPHOjxy4TUDjRh61MMoK2QvDNZqrMDXJYrEDZP/HPFvBubR24CQ0DBi5lgWhLxG4lvVsXPRDXtZ03+JgonoQ==", + "dev": true, + "peer": true, + "dependencies": { + "ioredis": ">=5" + } + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "license": "MIT" @@ -3619,7 +3676,6 @@ }, "node_modules/acorn-walk": { "version": "8.2.0", - "devOptional": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3764,7 +3820,6 @@ }, "node_modules/arg": { "version": "4.1.3", - "devOptional": true, "license": "MIT" }, "node_modules/argparse": { @@ -4877,7 +4932,6 @@ }, "node_modules/create-require": { "version": "1.1.1", - "devOptional": true, "license": "MIT" }, "node_modules/cron": { @@ -5180,7 +5234,6 @@ }, "node_modules/diff": { "version": "4.0.2", - "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -6197,6 +6250,32 @@ "bser": "2.1.1" } }, + "node_modules/fengari": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.4.tgz", + "integrity": "sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==", + "dev": true, + "dependencies": { + "readline-sync": "^1.4.9", + "sprintf-js": "^1.1.1", + "tmp": "^0.0.33" + } + }, + "node_modules/fengari-interop": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/fengari-interop/-/fengari-interop-0.1.3.tgz", + "integrity": "sha512-EtZ+oTu3kEwVJnoymFPBVLIbQcCoy9uWCVnMA6h3M/RqHkUBsLYp29+RRHf9rKr6GwjubWREU1O7RretFIXjHw==", + "dev": true, + "peerDependencies": { + "fengari": "^0.1.0" + } + }, + "node_modules/fengari/node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true + }, "node_modules/fetch-blob": { "version": "3.2.0", "funding": [ @@ -7103,6 +7182,26 @@ "url": "https://opencollective.com/ioredis" } }, + "node_modules/ioredis-mock": { + "version": "8.8.3", + "resolved": "https://registry.npmjs.org/ioredis-mock/-/ioredis-mock-8.8.3.tgz", + "integrity": "sha512-LkF17WIyYkPfUhvp0fjIZ+HKhILEoq1J2b71vv+9EOW054UlkySVEvgQ2RolXM+eI759MteHtXQvv0oRn0lkUg==", + "dev": true, + "dependencies": { + "@ioredis/as-callback": "^3.0.0", + "@ioredis/commands": "^1.2.0", + "fengari": "^0.1.4", + "fengari-interop": "^0.1.3", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12.22" + }, + "peerDependencies": { + "@types/ioredis-mock": "^8", + "ioredis": "^5" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "license": "MIT", @@ -8423,6 +8522,11 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-pretty-compact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-4.0.0.tgz", + "integrity": "sha512-3CNZ2DnrpByG9Nqj6Xo8vqbjT4F6N+tb4Gb28ESAZjYZ5yqvmc56J+/kuIwkaAMOyblTQhUW7PxMkUb8Q36N3Q==" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "license": "ISC" @@ -8679,7 +8783,6 @@ }, "node_modules/make-error": { "version": "1.3.6", - "devOptional": true, "license": "ISC" }, "node_modules/makeerror": { @@ -10114,6 +10217,15 @@ "node": ">=8.10.0" } }, + "node_modules/readline-sync": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", + "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -10551,8 +10663,9 @@ } }, "node_modules/semver": { - "version": "7.5.3", - "license": "ISC", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -11389,7 +11502,6 @@ }, "node_modules/ts-node": { "version": "10.9.1", - "devOptional": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -11899,7 +12011,6 @@ }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", - "devOptional": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -12355,7 +12466,6 @@ }, "node_modules/yn": { "version": "3.1.1", - "devOptional": true, "license": "MIT", "engines": { "node": ">=6" diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 9855c779..83804757 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -37,6 +37,8 @@ }, "homepage": "https://github.com/AmplicaLabs/content-publishing-service#readme", "dependencies": { + "@dsnp/activity-content": "^1.1.0", + "@dsnp/frequency-schemas": "^1.0.2", "@dsnp/parquetjs": "^1.3.4", "@frequency-chain/api-augment": "1.7.0", "@jest/globals": "^29.5.0", @@ -71,7 +73,7 @@ "ipfs-only-hash": "^4.0.0", "joi": "^17.9.1", "mime-types": "^2.1.35", - "multiformats": "^9.9.0", + "multiformats": "9.9.0", "rxjs": "^7.8.1", "time-constants": "^1.0.3" }, @@ -95,6 +97,7 @@ "eslint-plugin-nestjs": "^1.2.3", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", + "ioredis-mock": "^8.8.3", "jest": "^29.5.0", "license-report": "^6.4.0", "prettier": "^3.0.2", From 14da29bcbbf2cef0cd9a730b669725597f8e4047 Mon Sep 17 00:00:00 2001 From: Aramik Date: Wed, 6 Sep 2023 16:41:35 -0700 Subject: [PATCH 014/137] adde e2e tests for assets (#35) * adde e2e tests for assets * removed console log --- .../apps/api/src/api.controller.ts | 1 + .../apps/api/src/api.module.ts | 3 +- .../apps/api/src/development.controller.ts | 19 +- .../apps/api/test/app.e2e-spec.ts | 308 +++++++++++++++++- .../asset.processor.service.ts | 2 +- .../request.processor.service.ts | 1 - services/content-watcher/env.template | 7 + .../libs/common/src/utils/ipfs.client.ts | 2 +- 8 files changed, 320 insertions(+), 23 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index ea7518f6..6600c870 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -48,6 +48,7 @@ export class ApiController { .addFileTypeValidator({ fileType: DSNP_VALID_MIME_TYPES, }) + // TODO: add a validator to check overall uploaded size .addMaxSizeValidator({ // this is in bytes (2 GB) maxSize: 2 * 1000 * 1000 * 1000, diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 83492bd7..55b8a956 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -9,6 +9,7 @@ import { ConfigModule } from './config/config.module'; import { DevelopmentController } from './development.controller'; import { QueueConstants } from '../../../libs/common/src'; import { ApiService } from './api.service'; +import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; @Module({ imports: [ @@ -54,7 +55,7 @@ import { ApiService } from './api.service'; }), ScheduleModule.forRoot(), ], - providers: [ConfigService, ApiService], + providers: [ConfigService, ApiService, IpfsService], controllers: process.env?.ENABLE_DEV_CONTROLLER === 'true' ? [DevelopmentController, ApiController] : [ApiController], exports: [], }) diff --git a/services/content-watcher/apps/api/src/development.controller.ts b/services/content-watcher/apps/api/src/development.controller.ts index 05ccdaec..ff657c4c 100644 --- a/services/content-watcher/apps/api/src/development.controller.ts +++ b/services/content-watcher/apps/api/src/development.controller.ts @@ -8,12 +8,16 @@ import { Controller, Logger, Post, Body, Param, Query, HttpException, HttpStatus import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { QueueConstants } from '../../../libs/common/src'; +import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; @Controller('api/dev') export class DevelopmentController { private readonly logger: Logger; - constructor(@InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue) { + constructor( + @InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue, + private ipfsService: IpfsService, + ) { this.logger = new Logger(this.constructor.name); } @@ -24,4 +28,17 @@ export class DevelopmentController { this.logger.log(job); return job; } + + @Get('/asset/:assetId') + // eslint-disable-next-line consistent-return + async getAsset(@Param('assetId') assetId: string) { + try { + return this.ipfsService.getPinned(assetId); + } catch (error: any) { + if (error.response) { + console.error(error.response.data); + } + throw error; + } + } } diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index b763942c..f83afc72 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -4,11 +4,14 @@ import { INestApplication, ValidationPipe } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import request from 'supertest'; import { EventEmitter2 } from '@nestjs/event-emitter'; +import { randomFill } from 'crypto'; import { ApiModule } from '../src/api.module'; describe('AppController E2E request verification!', () => { let app: INestApplication; let module: TestingModule; + // eslint-disable-next-line no-promise-executor-return + const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); const validLocation = { name: 'name of location', accuracy: 97, @@ -28,18 +31,25 @@ describe('AppController E2E request verification!', () => { name: '#taggedUser', }, ]; - const validContentNoAssets = { + const validContentNoUploadedAssets = { content: 'test broadcast message', published: '1970-01-01T00:00:00+00:00', name: 'name of note content', + assets: [ + { + type: 'link', + name: 'link asset', + href: 'http://example.com', + }, + ], tag: validTags, location: validLocation, }; - const validBroadCastNoAssets = { - content: validContentNoAssets, + const validBroadCastNoUploadedAssets = { + content: validContentNoUploadedAssets, }; - const validReplyNoAssets = { - content: validContentNoAssets, + const validReplyNoUploadedAssets = { + content: validContentNoUploadedAssets, inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', }; const validReaction = { @@ -47,7 +57,7 @@ describe('AppController E2E request verification!', () => { apply: 5, inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', }; - const validProfileNoAssets = { + const validProfileNoUploadedAssets = { summary: 'profile summary', published: '1970-01-01T00:00:00+00:00', name: 'name of profile content', @@ -77,7 +87,7 @@ describe('AppController E2E request verification!', () => { const invalidDsnpUserId = '2gsjhdaj'; return request(app.getHttpServer()) .post(`/api/content/${invalidDsnpUserId}/broadcast`) - .send(validBroadCastNoAssets) + .send(validBroadCastNoUploadedAssets) .expect(400) .expect((res) => expect(res.text).toContain('must be a number string')); }); @@ -92,13 +102,67 @@ describe('AppController E2E request verification!', () => { }); describe('(POST) /api/content/:dsnpUserId/broadcast', () => { - it('valid request without assets should work!', () => + it('valid request without uploaded assets should work!', () => request(app.getHttpServer()) .post(`/api/content/123/broadcast`) - .send(validBroadCastNoAssets) + .send(validBroadCastNoUploadedAssets) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); + it('valid request with uploaded assets should work!', async () => { + const file = Buffer.from('g'.repeat(30 * 1000 * 1000)); // 30MB + const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); + await sleep(1000); + const validBroadCastWithUploadedAssets = { + content: { + ...validContentNoUploadedAssets, + assets: [ + { + type: 'image', + name: 'image asset', + references: [ + { + referenceId: response.body.assetIds[0], + height: 123, + width: 321, + }, + ], + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(validBroadCastWithUploadedAssets) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId')); + }, 15000); + + it('request with not uploaded assets should fail!', async () => { + const badAssetCid = 'bafybeiap642764aat6txaap4qex4empkdtpjv7uabv47w1pdih3nflajpy'; + const validBroadCastWithUploadedAssets = { + content: { + ...validContentNoUploadedAssets, + assets: [ + { + type: 'image', + name: 'image asset', + references: [ + { + referenceId: badAssetCid, + }, + ], + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(validBroadCastWithUploadedAssets) + .expect(400) + .expect((res) => expect(res.text).toContain(`${badAssetCid} does not exist`)); + }); + it('empty body should fail', () => { const body = {}; return request(app.getHttpServer()) @@ -349,10 +413,68 @@ describe('AppController E2E request verification!', () => { it('valid request without assets should work!', () => request(app.getHttpServer()) .post(`/api/content/123/reply`) - .send(validReplyNoAssets) + .send(validReplyNoUploadedAssets) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); + it('valid request with uploaded assets should work!', async () => { + const file = Buffer.from('h'.repeat(30 * 1000 * 1000)); // 30MB + const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); + await sleep(1000); + const validReplyWithUploadedAssets = { + ...validReplyNoUploadedAssets, + content: { + ...validContentNoUploadedAssets, + assets: [ + { + type: 'image', + name: 'image asset', + references: [ + { + referenceId: response.body.assetIds[0], + height: 123, + width: 321, + }, + ], + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/reply`) + .send(validReplyWithUploadedAssets) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId')); + }, 15000); + + it('request with not uploaded assets should fail!', async () => { + const badAssetCid = 'bafybeiap642764aat6txaap4qex4empkdtpjv7uabv47w1pdih3nflajpy'; + const validReplyWithUploadedAssets = { + ...validReplyNoUploadedAssets, + content: { + ...validContentNoUploadedAssets, + assets: [ + { + type: 'image', + name: 'image asset', + references: [ + { + referenceId: badAssetCid, + height: 123, + width: 321, + }, + ], + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/reply`) + .send(validReplyWithUploadedAssets) + .expect(400) + .expect((res) => expect(res.text).toContain(`${badAssetCid} does not exist`)); + }); + it('empty body should fail', () => { const body = {}; return request(app.getHttpServer()) @@ -366,7 +488,7 @@ describe('AppController E2E request verification!', () => { request(app.getHttpServer()) .post(`/api/content/123/reply`) .send({ - content: validContentNoAssets, + content: validContentNoUploadedAssets, }) .expect(400) .expect((res) => expect(res.text).toContain('inReplyTo must be a string'))); @@ -375,7 +497,7 @@ describe('AppController E2E request verification!', () => { request(app.getHttpServer()) .post(`/api/content/123/reply`) .send({ - content: validContentNoAssets, + content: validContentNoUploadedAssets, inReplyTo: 'shgdjas72gsjajasa', }) .expect(400) @@ -427,17 +549,73 @@ describe('AppController E2E request verification!', () => { .put(`/api/content/123/0x7653423447AF`) .send({ targetAnnouncementType: 'broadcast', - content: validContentNoAssets, + content: validContentNoUploadedAssets, }) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); + it('valid request with uploaded assets should work!', async () => { + const file = Buffer.from('g'.repeat(30 * 1000 * 1000)); // 30MB + const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); + await sleep(1000); + const validContentWithUploadedAssets = { + ...validContentNoUploadedAssets, + assets: [ + { + type: 'image', + name: 'image asset', + references: [ + { + referenceId: response.body.assetIds[0], + height: 123, + width: 321, + }, + ], + }, + ], + }; + return request(app.getHttpServer()) + .put(`/api/content/123/0x7653423447AF`) + .send({ + targetAnnouncementType: 'broadcast', + content: validContentWithUploadedAssets, + }) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId')); + }, 15000); + + it('request with not uploaded assets should fail!', async () => { + const badAssetCid = 'bafybeiap642764aat6txaap4qex4empkdtpjv7uabv47w1pdih3nflajpy'; + const validBroadCastWithUploadedAssets = { + ...validContentNoUploadedAssets, + assets: [ + { + type: 'image', + name: 'image asset', + references: [ + { + referenceId: badAssetCid, + }, + ], + }, + ], + }; + return request(app.getHttpServer()) + .put(`/api/content/123/0x7653423447AF`) + .send({ + targetAnnouncementType: 'broadcast', + content: validBroadCastWithUploadedAssets, + }) + .expect(400) + .expect((res) => expect(res.text).toContain(`${badAssetCid} does not exist`)); + }); + it('invalid targetAnnouncementType should fail', () => request(app.getHttpServer()) .put(`/api/content/123/0x7653423447AF`) .send({ targetAnnouncementType: 'invalid', - content: validContentNoAssets, + content: validContentNoUploadedAssets, }) .expect(400) .expect((res) => expect(res.text).toContain('targetAnnouncementType must be one of the following values'))); @@ -456,11 +634,78 @@ describe('AppController E2E request verification!', () => { request(app.getHttpServer()) .put(`/api/profile/123`) .send({ - profile: validProfileNoAssets, + profile: validProfileNoUploadedAssets, }) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); + it('valid request with uploaded assets should work!', async () => { + const file = Buffer.from('n'.repeat(30 * 1000 * 1000)); // 30MB + const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file.jpg').expect(202); + await sleep(1000); + const validUploadWithUploadedAssets = { + ...validProfileNoUploadedAssets, + icon: [ + { + referenceId: response.body.assetIds[0], + height: 123, + width: 321, + }, + ], + }; + return request(app.getHttpServer()) + .put(`/api/profile/123`) + .send({ + profile: validUploadWithUploadedAssets, + }) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId')); + }, 15000); + + it('request with not uploaded icon should fail!', async () => { + const badAssetCid = 'bafybeiap642764aat6txaap4qex4empkdtpjv7uabv47w1pdih3nflajpy'; + const validUploadWithUploadedAssets = { + ...validProfileNoUploadedAssets, + icon: [ + { + referenceId: badAssetCid, + height: 123, + width: 321, + }, + ], + }; + return request(app.getHttpServer()) + .put(`/api/profile/123`) + .send({ + profile: validUploadWithUploadedAssets, + }) + .expect(400) + .expect((res) => expect(res.text).toContain(`${badAssetCid} does not exist`)); + }); + + it('request with non-image uploaded assets should fail!', async () => { + const file = Buffer.from('s'.repeat(30 * 1000 * 1000)); // 30MB + const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file.mp3').expect(202); + await sleep(1000); + const profileContent = { + ...validProfileNoUploadedAssets, + icon: [ + { + referenceId: response.body.assetIds[0], + height: 123, + width: 321, + }, + ], + }; + return request(app.getHttpServer()) + .put(`/api/profile/123`) + .send({ + profile: profileContent, + }) + .expect(400) + .expect((res) => expect(res.text).toContain('is not an image!')); + }, 15000); + it('empty profile should fail', () => request(app.getHttpServer()) .put(`/api/profile/123`) @@ -502,16 +747,43 @@ describe('AppController E2E request verification!', () => { it('valid request should work!', () => request(app.getHttpServer()) .put(`/api/asset/upload`) - .attach('files', Buffer.from(validContentNoAssets.toString()), 'image.jpg') + .attach('files', Buffer.from(validContentNoUploadedAssets.toString()), 'image.jpg') .expect(202) .expect((res) => expect(res.text).toContain('assetIds'))); it('invalid mime should fail', () => request(app.getHttpServer()) .put(`/api/asset/upload`) - .attach('files', Buffer.from(validContentNoAssets.toString()), 'doc.txt') + .attach('files', Buffer.from(validContentNoUploadedAssets.toString()), 'doc.txt') .expect(422) .expect((res) => expect(res.text).toContain('expected type is'))); + + it('valid request should work!', async () => { + const file1 = Buffer.from('a'.repeat(30 * 1000)); // 30KB + const file2 = Buffer.from('t'.repeat(30 * 1000 * 1000)); // 30MB + const file3 = Buffer.from('z'.repeat(100 * 1000 * 1000)); // 100MB + await request(app.getHttpServer()) + .put(`/api/asset/upload`) + .attach('files', file1, 'file1.jpg') + .attach('files', file2, 'file2.mp3') + .attach('files', file3, 'file3.mpeg') + .expect(202) + .expect((res) => expect(res.text).toContain('assetIds')); + }, 15000); + + it('upload asset should be uploaded to IPFS', async () => { + const buffer = new Uint32Array(100 * 1000); + randomFill(buffer, (err, buf) => { + if (err) throw err; + }); + const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', Buffer.from(buffer), 'file1.jpg').expect(202); + const assetId = response.body.assetIds[0]; + await sleep(2000); + return request(app.getHttpServer()) + .get(`/api/dev/asset/${assetId}`) + .expect(200) + .expect((res) => expect(Buffer.from(res.body)).toEqual(Buffer.from(buffer))); + }, 15000); }); afterEach(async () => { @@ -520,5 +792,5 @@ describe('AppController E2E request verification!', () => { } catch (err) { console.error(err); } - }); + }, 15000); }); diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts index 045850c0..4617bfcb 100644 --- a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts +++ b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts @@ -43,7 +43,7 @@ export class AssetProcessorService extends WorkerHost { const secondsPassed = Math.round((Date.now() - job.timestamp) / 1000); const expectedSecondsToExpire = 5 * 60; // TODO: get from config const secondsToExpire = Math.max(0, expectedSecondsToExpire - secondsPassed); - const result = await this.redis.pipeline().del(job.data.contentLocation).expire(job.data.metadataLocation, secondsToExpire, 'LT').exec(); + const result = await this.redis.pipeline().expire(job.data.contentLocation, secondsToExpire, 'LT').expire(job.data.metadataLocation, secondsToExpire, 'LT').exec(); this.logger.debug(result); } } diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts index 1abfca4f..1bf598d0 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts @@ -28,7 +28,6 @@ export class RequestProcessorService extends WorkerHost { const pinnedAssets = assets.map((cid) => this.ipfsService.getPinned(cid)); const pinnedResult = await Promise.all(pinnedAssets); - this.logger.debug(pinnedResult); // if any of assets does not exists delay the job for a future attempt if (pinnedResult.some((buffer) => !buffer)) { await this.delayJobAndIncrementAttempts(job); diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index bc6fd8a1..7ce43d61 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -16,3 +16,10 @@ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRIES=4 CAPACITY_LIMIT='{"type":"percentage", "value":80}' +IPFS_ENDPOINT="http://127.0.0.1:5001" +IPFS_BASIC_AUTH_USER="" +IPFS_BASIC_AUTH_SECRET="" +IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" + +# should be enabled for e2e tests +# ENABLE_DEV_CONTROLLER=true \ No newline at end of file diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index 7cc9c320..68107c5f 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -88,7 +88,7 @@ export class IpfsService { authorization: ipfsAuth, }; - const response = await axios.post(ipfsGet, null, { headers, responseType: 'arraybuffer' }); + const response = await axios.post(ipfsGet, null, { headers, responseType: 'blob' }); const { data } = response; return data; From dc1fdd12c32c5ba3a5d22e9aea06a5f1090615e4 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Wed, 6 Sep 2023 18:43:26 -0500 Subject: [PATCH 015/137] bug fix (#36) --- .../worker/src/batch_announcer/batch.announcer.spec.ts | 2 +- .../apps/worker/src/batch_announcer/batch.announcer.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts index 52ffefdc..edd37793 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts @@ -72,7 +72,7 @@ describe('BatchAnnouncer', () => { mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); mockBlockchainService.getSchema.mockReturnValue({ model: JSON.stringify(broadcast) }); mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); - mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', size: 'mockSize' }); + mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', size: 10, hash: 'mockHash' }); const batchJob = { batchId: 'mockBatchId', diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts index 1f39cedf..bbb07956 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts @@ -55,11 +55,11 @@ export class BatchAnnouncer { await writer.close(); const buffer = await this.bufferPublishStream(publishStream); - const [cid, hash] = await this.pinStringToIPFS(buffer); + const [cid, hash, size] = await this.pinParquetFileToIPFS(buffer); const ipfsUrl = await this.formIpfsUrl(cid); this.logger.debug(`Batch ${batchId} published to IPFS at ${ipfsUrl}`); this.logger.debug(`Batch ${batchId} hash: ${hash}`); - return { id: batchId, schemaId, data: { cid, payloadLength: buffer.length } }; + return { id: batchId, schemaId, data: { cid, payloadLength: size } }; } private async bufferPublishStream(publishStream: PassThrough): Promise { @@ -78,9 +78,9 @@ export class BatchAnnouncer { }); } - private async pinStringToIPFS(buf: Buffer): Promise<[string, string]> { - const { cid, size } = await this.ipfsService.ipfsPin('application/octet-stream', buf); - return [cid.toString(), size.toString()]; + private async pinParquetFileToIPFS(buf: Buffer): Promise<[string, string, number]> { + const { cid, hash, size } = await this.ipfsService.ipfsPin('application/octet-stream', buf); + return [cid.toString(), hash, size]; } private async formIpfsUrl(cid: string): Promise { From 5a46124a89f4092beb50269cb25f56f771767867 Mon Sep 17 00:00:00 2001 From: Aramik Date: Thu, 7 Sep 2023 12:36:28 -0700 Subject: [PATCH 016/137] fix tombstone contract (#38) * fix tombstone contract * fixed e2e tests * comment fix * comment fix --- .../apps/api/src/api.controller.ts | 14 ++--- .../apps/api/src/api.service.ts | 5 +- .../content-watcher/apps/api/src/metadata.ts | 2 +- .../apps/api/test/app.e2e-spec.ts | 61 ++++++++++++++----- .../batch_announcer/batch.announcer.spec.ts | 2 + .../libs/common/src/dtos/announcement.dto.ts | 29 ++++++--- .../libs/common/src/dtos/common.dto.ts | 8 +-- .../libs/common/src/dtos/validation.dto.ts | 5 ++ .../src/interfaces/request-job.interface.ts | 1 - 9 files changed, 86 insertions(+), 41 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 6600c870..c06586b0 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -8,12 +8,12 @@ import { AssetIncludedRequestDto, BroadcastDto, DSNP_VALID_MIME_TYPES, - DsnpContentHashParam, DsnpUserIdParam, FilesUploadDto, ProfileDto, ReactionDto, ReplyDto, + TombstoneDto, UpdateDto, UploadResponseDto, } from '../../../libs/common/src'; @@ -83,17 +83,17 @@ export class ApiController { return this.apiService.enqueueRequest(AnnouncementTypeDto.REACTION, userDsnpId.userDsnpId, reactionDto); } - @Put('content/:userDsnpId/:targetContentHash') + @Put('content/:userDsnpId') @HttpCode(202) - async update(@Param() userDsnpId: DsnpUserIdParam, @Param() targetContentHash: DsnpContentHashParam, @Body() updateDto: UpdateDto): Promise { + async update(@Param() userDsnpId: DsnpUserIdParam, @Body() updateDto: UpdateDto): Promise { await this.apiService.validateAssets(updateDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.UPDATE, userDsnpId.userDsnpId, updateDto, targetContentHash.targetContentHash); + return this.apiService.enqueueRequest(AnnouncementTypeDto.UPDATE, userDsnpId.userDsnpId, updateDto); } - @Delete('content/:userDsnpId/:targetContentHash') + @Delete('content/:userDsnpId') @HttpCode(202) - async delete(@Param() userDsnpId: DsnpUserIdParam, @Param() targetContentHash: DsnpContentHashParam): Promise { - return this.apiService.enqueueRequest(AnnouncementTypeDto.TOMBSTONE, userDsnpId.userDsnpId, undefined, targetContentHash.targetContentHash); + async delete(@Param() userDsnpId: DsnpUserIdParam, @Body() tombstoneDto: TombstoneDto): Promise { + return this.apiService.enqueueRequest(AnnouncementTypeDto.TOMBSTONE, userDsnpId.userDsnpId, tombstoneDto); } @Put('profile/:userDsnpId') diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index ee1abbc9..d985757f 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -1,5 +1,5 @@ import { InjectQueue } from '@nestjs/bullmq'; -import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { Queue } from 'bullmq'; import { createHash } from 'crypto'; import { BulkJobOptions } from 'bullmq/dist/esm/interfaces'; @@ -34,13 +34,12 @@ export class ApiService { this.logger = new Logger(this.constructor.name); } - async enqueueRequest(announcementType: AnnouncementTypeDto, dsnpUserId: string, content?: RequestTypeDto, targetContentHash?: string): Promise { + async enqueueRequest(announcementType: AnnouncementTypeDto, dsnpUserId: string, content?: RequestTypeDto): Promise { const data = { content, id: '', announcementType, dsnpUserId, - targetContentHash, dependencyAttempt: 0, } as IRequestJob; data.id = this.calculateJobId(data); diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index 9577d58d..530eafc4 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -5,5 +5,5 @@ export default async () => { ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), ["../../../libs/common/src/dtos/common.dto"]: await import("../../../libs/common/src/dtos/common.dto") }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "UpdateDto": { targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].UpdateAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "DsnpContentHashParam": { targetContentHash: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }], [import("./development.controller"), { "DevelopmentController": { "requestJob": {} } }]] } }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }], [import("./development.controller"), { "DevelopmentController": { "requestJob": {}, "getAsset": { type: Object } } }]] } }; }; \ No newline at end of file diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index f83afc72..8aba5856 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -91,14 +91,6 @@ describe('AppController E2E request verification!', () => { .expect(400) .expect((res) => expect(res.text).toContain('must be a number string')); }); - - it('invalid DsnpContentHashParam should fail', () => { - const invalidContentHashParam = '2gsjhdaj'; - return request(app.getHttpServer()) - .delete(`/api/content/123/${invalidContentHashParam}`) - .expect(400) - .expect((res) => expect(res.text).toContain('must be in hexadecimal format')); - }); }); describe('(POST) /api/content/:dsnpUserId/broadcast', () => { @@ -543,11 +535,12 @@ describe('AppController E2E request verification!', () => { .expect((res) => expect(res.text).toContain('inReplyTo must match'))); }); - describe('(PUT) /api/content/:dsnpUserId/:contentHash', () => { + describe('(PUT) /api/content/:dsnpUserId', () => { it('valid request without assets should work!', () => request(app.getHttpServer()) - .put(`/api/content/123/0x7653423447AF`) + .put(`/api/content/123`) .send({ + targetContentHash: '0x7653423447AF', targetAnnouncementType: 'broadcast', content: validContentNoUploadedAssets, }) @@ -575,8 +568,9 @@ describe('AppController E2E request verification!', () => { ], }; return request(app.getHttpServer()) - .put(`/api/content/123/0x7653423447AF`) + .put(`/api/content/123`) .send({ + targetContentHash: '0x7653423447AF', targetAnnouncementType: 'broadcast', content: validContentWithUploadedAssets, }) @@ -601,8 +595,9 @@ describe('AppController E2E request verification!', () => { ], }; return request(app.getHttpServer()) - .put(`/api/content/123/0x7653423447AF`) + .put(`/api/content/123`) .send({ + targetContentHash: '0x7653423447AF', targetAnnouncementType: 'broadcast', content: validBroadCastWithUploadedAssets, }) @@ -612,21 +607,57 @@ describe('AppController E2E request verification!', () => { it('invalid targetAnnouncementType should fail', () => request(app.getHttpServer()) - .put(`/api/content/123/0x7653423447AF`) + .put(`/api/content/123`) .send({ + targetContentHash: '0x7653423447AF', targetAnnouncementType: 'invalid', content: validContentNoUploadedAssets, }) .expect(400) .expect((res) => expect(res.text).toContain('targetAnnouncementType must be one of the following values'))); + + it('invalid targetContentHash should fail', () => + request(app.getHttpServer()) + .put(`/api/content/123`) + .send({ + targetContentHash: '6328462378', + targetAnnouncementType: 'reply', + content: validContentNoUploadedAssets, + }) + .expect(400) + .expect((res) => expect(res.text).toContain('targetContentHash must be in hexadecimal format!'))); }); - describe('(DELETE) /api/content/:dsnpUserId/contentHash', () => { + describe('(DELETE) /api/content/:dsnpUserId', () => { it('valid request should work!', () => request(app.getHttpServer()) - .delete(`/api/content/123/0x7653423447AF`) + .delete(`/api/content/123`) + .send({ + targetContentHash: '0x7653423447AF', + targetAnnouncementType: 'reply', + }) .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); + + it('invalid targetAnnouncementType should fail', () => + request(app.getHttpServer()) + .delete(`/api/content/123`) + .send({ + targetContentHash: '0x7653423447AF', + targetAnnouncementType: 'invalid', + }) + .expect(400) + .expect((res) => expect(res.text).toContain('targetAnnouncementType must be one of the following values'))); + + it('invalid targetContentHash should fail', () => + request(app.getHttpServer()) + .delete(`/api/content/123`) + .send({ + targetContentHash: '6328462378', + targetAnnouncementType: 'reply', + }) + .expect(400) + .expect((res) => expect(res.text).toContain('targetContentHash must be in hexadecimal format!'))); }); describe('(PUT) /api/profile/:userDsnpId', () => { diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts index edd37793..58e022d2 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts @@ -1,6 +1,8 @@ import { expect, describe, jest, it, beforeEach } from '@jest/globals'; import assert from 'assert'; +// eslint-disable-next-line import/no-extraneous-dependencies import { FrequencyParquetSchema } from '@dsnp/frequency-schemas/types/frequency'; +// eslint-disable-next-line import/no-extraneous-dependencies import Redis from 'ioredis-mock'; import { BatchAnnouncer } from './batch.announcer'; diff --git a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts index d9464527..7cf3c587 100644 --- a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts @@ -2,13 +2,13 @@ * File name should always end with `.dto.ts` for swagger metadata generator to get picked up */ // eslint-disable-next-line max-classes-per-file -import { IsEnum, IsInt, IsNotEmpty, IsString, Matches, Max, MaxLength, Min, MinLength, ValidateNested } from 'class-validator'; +import { IsEnum, IsHexadecimal, IsInt, IsNotEmpty, IsString, Matches, Max, MaxLength, Min, MinLength, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { NoteActivityDto, ProfileActivityDto } from './activity.dto'; -import { DSNP_CONTENT_URI_REGEX, DSNP_EMOJI_REGEX } from './validation.dto'; +import { DSNP_CONTENT_HASH_REGEX, DSNP_CONTENT_URI_REGEX, DSNP_EMOJI_REGEX } from './validation.dto'; // eslint-disable-next-line no-shadow -export enum UpdateAnnouncementTypeDto { +export enum ModifiableAnnouncementTypeDto { BROADCAST = 'broadcast', REPLY = 'reply', } @@ -31,9 +31,24 @@ export class ReplyDto { content: NoteActivityDto; } +export class TombstoneDto { + @IsString() + @IsNotEmpty() + @Matches(DSNP_CONTENT_HASH_REGEX, { message: 'targetContentHash must be in hexadecimal format!' }) + targetContentHash: string; + + @IsEnum(ModifiableAnnouncementTypeDto) + targetAnnouncementType: ModifiableAnnouncementTypeDto; +} + export class UpdateDto { - @IsEnum(UpdateAnnouncementTypeDto) - targetAnnouncementType: UpdateAnnouncementTypeDto; + @IsString() + @IsNotEmpty() + @Matches(DSNP_CONTENT_HASH_REGEX, { message: 'targetContentHash must be in hexadecimal format!' }) + targetContentHash: string; + + @IsEnum(ModifiableAnnouncementTypeDto) + targetAnnouncementType: ModifiableAnnouncementTypeDto; @IsNotEmpty() @ValidateNested() @@ -64,5 +79,5 @@ export class ProfileDto { profile: ProfileActivityDto; } -export type RequestTypeDto = BroadcastDto | ReplyDto | ReactionDto | UpdateDto | ProfileDto; -export type AssetIncludedRequestDto = BroadcastDto & ReplyDto & UpdateDto & ProfileDto; +export type RequestTypeDto = BroadcastDto | ReplyDto | ReactionDto | UpdateDto | ProfileDto | TombstoneDto; +export type AssetIncludedRequestDto = BroadcastDto & ReplyDto & UpdateDto & ProfileDto & TombstoneDto; diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index 68040caf..dc1ddadd 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -3,7 +3,7 @@ */ // eslint-disable-next-line max-classes-per-file import { ApiProperty } from '@nestjs/swagger'; -import { IsHexadecimal, IsNotEmpty, IsNumberString } from 'class-validator'; +import { IsNotEmpty, IsNumberString } from 'class-validator'; export class DsnpUserIdParam { @IsNotEmpty() @@ -11,12 +11,6 @@ export class DsnpUserIdParam { userDsnpId: string; } -export class DsnpContentHashParam { - @IsNotEmpty() - @IsHexadecimal({ message: 'targetContentHash must be in hexadecimal format!' }) - targetContentHash: string; -} - export class AnnouncementResponseDto { referenceId: string; } diff --git a/services/content-watcher/libs/common/src/dtos/validation.dto.ts b/services/content-watcher/libs/common/src/dtos/validation.dto.ts index 3b2e0fee..9df5b40c 100644 --- a/services/content-watcher/libs/common/src/dtos/validation.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/validation.dto.ts @@ -7,6 +7,11 @@ * - example: 1970-01-01T00:00:00+00:00 */ export const ISO8601_REGEX = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,})?(Z|[+-][01][0-9]:[0-5][0-9])?$/; +/** + * DSNP content hash based on DSNP Spec + * example: 0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef + */ +export const DSNP_CONTENT_HASH_REGEX = /^0x[0-9a-f]+$/i; /** * DSNP user URI based on DSNP Spec * example: dsnp://78187493520 diff --git a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts index 55593ec5..fb44bf05 100644 --- a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts @@ -5,7 +5,6 @@ export interface IRequestJob { id: string; announcementType: AnnouncementTypeDto; dsnpUserId: string; - targetContentHash?: string; content?: RequestTypeDto; dependencyAttempt: number; } From c7548c1f6b428d98a15bb5579d8cae4bd27c338a Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Fri, 8 Sep 2023 14:02:09 -0500 Subject: [PATCH 017/137] Request processor to queue respective announcements (#37) * request processor * queue announcements * cleanup * add unit test/ add todo * fix tombstone contract * mimeType for assest required * address some feedback * feedback * refactor * address feedback * fix tags * lint * add unit tests * add more tests * cleanup/lint * Update apps/worker/src/request_processor/dsnp.announcement.processor.ts Co-authored-by: Aramik --------- Co-authored-by: Aramik --- .../dsnp.announcement.processor.spec.ts | 204 ++++++++++ .../dsnp.announcement.processor.ts | 383 ++++++++++++++++++ .../request.processor.module.ts | 23 +- .../request.processor.service.ts | 30 +- .../libs/common/src/interfaces/dsnp.ts | 42 +- .../common/src/utils/dsnpTypeConverter.ts | 176 -------- 6 files changed, 664 insertions(+), 194 deletions(-) create mode 100644 services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts create mode 100644 services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts delete mode 100644 services/content-watcher/libs/common/src/utils/dsnpTypeConverter.ts diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts new file mode 100644 index 00000000..42119ad1 --- /dev/null +++ b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts @@ -0,0 +1,204 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { Queue } from 'bullmq'; +import { expect, describe, it, beforeEach, jest } from '@jest/globals'; +import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; +import { AnnouncementTypeDto, IRequestJob, ModifiableAnnouncementTypeDto, TagTypeDto } from '../../../../libs/common/src'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; + +const mockQueue = { + add: jest.fn(), +}; + +// Mock the ConfigService class +const mockConfigService = { + getIpfsCidPlaceholder: jest.fn(), +}; + +// Mock the IpfsService class +const mockIpfsService = { + getPinned: jest.fn(), + ipfsPin: jest.fn(), +}; + +describe('DsnpAnnouncementProcessor', () => { + let dsnpAnnouncementProcessor: DsnpAnnouncementProcessor; + let module: TestingModule; + + beforeEach(async () => { + module = await Test.createTestingModule({ + providers: [ + DsnpAnnouncementProcessor, + { provide: ConfigService, useValue: mockConfigService }, + { provide: IpfsService, useValue: mockIpfsService }, + { provide: Queue, useValue: mockQueue }, + { provide: 'BullQueue_assetQueue', useValue: mockQueue }, + { provide: 'BullQueue_broadcastQueue', useValue: mockQueue }, + { provide: 'BullQueue_replyQueue', useValue: mockQueue }, + { provide: 'BullQueue_reactionQueue', useValue: mockQueue }, + { provide: 'BullQueue_updateQueue', useValue: mockQueue }, + { provide: 'BullQueue_profileQueue', useValue: mockQueue }, + { provide: 'BullQueue_tombstoneQueue', useValue: mockQueue }, + ], + }).compile(); + + dsnpAnnouncementProcessor = module.get(DsnpAnnouncementProcessor); + }); + + it('should be defined', () => { + expect(dsnpAnnouncementProcessor).toBeDefined(); + }); + + it('should collect and queue a broadcast announcement', async () => { + // Mock the necessary dependencies' behavior + mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); + mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); + mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); + + const data: IRequestJob = { + id: '1', + announcementType: AnnouncementTypeDto.BROADCAST, + dsnpUserId: 'dsnp://123', + dependencyAttempt: 0, + content: { + content: { + content: 'mockContent', + published: '2021-01-01T00:00:00.000Z', + }, + }, + }; + + await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); + + expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); + expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); + }); + it('should collect and queue a reply announcement', async () => { + // Mock the necessary dependencies' behavior + mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); + mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); + mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); + + const data: IRequestJob = { + id: '2', + announcementType: AnnouncementTypeDto.REPLY, + dsnpUserId: 'dsnp://456', + dependencyAttempt: 0, + content: { + content: { + content: 'mockReplyContent', + published: '2021-01-01T00:00:00.000Z', + }, + inReplyTo: 'dsnp://123/reply/1', + }, + }; + + await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); + + expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); + expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); + }); + + it('should collect and queue a reaction announcement', async () => { + // Mock the necessary dependencies' behavior + mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); + mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); + mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); + + const data: IRequestJob = { + id: '3', + announcementType: AnnouncementTypeDto.REACTION, + dsnpUserId: 'dsnp://789', + dependencyAttempt: 0, + content: { + emoji: '👍', + inReplyTo: 'dsnp://123/reply/1', + apply: 10, + }, + }; + + await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); + + expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); + expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); + }); + it('should collect and queue an update announcement', async () => { + // Mock the necessary dependencies' behavior + mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); + mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); + mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); + + const data: IRequestJob = { + id: '4', + announcementType: AnnouncementTypeDto.UPDATE, + dsnpUserId: 'dsnp://101', + dependencyAttempt: 0, + content: { + content: { + content: 'mockUpdateContent', + published: '2021-01-01T00:00:00.000Z', + }, + targetAnnouncementType: ModifiableAnnouncementTypeDto.REPLY, + targetContentHash: 'dsnp://123/reply/1', + }, + }; + + await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); + + expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); + expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); + }); + + it('should collect and queue a profile announcement', async () => { + // Mock the necessary dependencies' behavior + mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); + mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); + mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); + + const data: IRequestJob = { + id: '5', + announcementType: AnnouncementTypeDto.PROFILE, + dsnpUserId: 'dsnp://789', + dependencyAttempt: 0, + content: { + profile: { + name: 'John Doe', + published: '2021-01-01T00:00:00.000Z', + summary: 'A brief summary', + tag: [ + { type: TagTypeDto.Hashtag, name: 'tag1' }, + { type: TagTypeDto.Mention, name: 'user1', mentionedId: 'dsnp://123' }, + ], + }, + }, + }; + + await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); + + expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); + expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); + }); + + it('should collect and queue a tombstone announcement', async () => { + // Mock the necessary dependencies' behavior + mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); + mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); + mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); + + const data: IRequestJob = { + id: '6', + announcementType: AnnouncementTypeDto.TOMBSTONE, + dsnpUserId: 'dsnp://999', + dependencyAttempt: 0, + content: { + targetAnnouncementType: ModifiableAnnouncementTypeDto.BROADCAST, + targetContentHash: 'dsnp://123/broadcast/1', + }, + }; + + await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); + + expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); + expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); + }); +}); diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts new file mode 100644 index 00000000..cd0952f0 --- /dev/null +++ b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts @@ -0,0 +1,383 @@ +import { + ActivityContentTag, + ActivityContentAttachment, + ActivityContentLink, + ActivityContentImageLink, + ActivityContentImage, + ActivityContentVideoLink, + ActivityContentVideo, + ActivityContentAudioLink, + ActivityContentAudio, + ActivityContentProfile, +} from '@dsnp/activity-content/types'; +import { Injectable, Logger } from '@nestjs/common'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Queue } from 'bullmq'; +import { + TagTypeDto, + AssetDto, + AttachmentTypeDto, + IRequestJob, + QueueConstants, + BroadcastDto, + ProfileDto, + ReactionDto, + ReplyDto, + UpdateDto, + AnnouncementTypeDto, + TombstoneDto, + ModifiableAnnouncementTypeDto, +} from '../../../../libs/common/src'; +import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { calculateDsnpHash } from '../../../../libs/common/src/utils/ipfs'; +import { + AnnouncementType, + BroadcastAnnouncement, + ProfileAnnouncement, + ReactionAnnouncement, + ReplyAnnouncement, + UpdateAnnouncement, + createBroadcast, + createNote, + createProfile, + createReaction, + createReply, + createTombstone, + createUpdate, +} from '../../../../libs/common/src/interfaces/dsnp'; + +@Injectable() +export class DsnpAnnouncementProcessor { + private logger: Logger; + + constructor( + @InjectQueue(QueueConstants.ASSET_QUEUE_NAME) private assetQueue: Queue, + @InjectQueue(QueueConstants.BROADCAST_QUEUE_NAME) private broadcastQueue: Queue, + @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, + @InjectQueue(QueueConstants.REACTION_QUEUE_NAME) private reactionQueue: Queue, + @InjectQueue(QueueConstants.UPDATE_QUEUE_NAME) private updateQueue: Queue, + @InjectQueue(QueueConstants.PROFILE_QUEUE_NAME) private profileQueue: Queue, + @InjectQueue(QueueConstants.TOMBSTONE_QUEUE_NAME) private tombstoneQueue: Queue, + private configService: ConfigService, + private ipfsService: IpfsService, + ) { + this.logger = new Logger(DsnpAnnouncementProcessor.name); + } + + public async collectAnnouncementAndQueue(data: IRequestJob) { + this.logger.debug(`Collecting announcement and queuing`); + this.logger.verbose(`Processing Activity: ${data.announcementType} for ${data.dsnpUserId}`); + try { + switch (data.announcementType) { + case AnnouncementTypeDto.BROADCAST: + await this.queueBroadcast(data); + break; + case AnnouncementTypeDto.REPLY: + await this.queueReply(data); + break; + case AnnouncementTypeDto.REACTION: + await this.queueReaction(data); + break; + case AnnouncementTypeDto.UPDATE: + await this.queueUpdate(data); + break; + case AnnouncementTypeDto.PROFILE: + await this.queueProfile(data); + break; + case AnnouncementTypeDto.TOMBSTONE: + await this.queueTombstone(data); + break; + default: + throw new Error(`Unsupported announcement type ${typeof data.announcementType}`); + } + } catch (e) { + this.logger.error(`Error processing announcement ${data.announcementType} for ${data.dsnpUserId}: ${e}`); + throw e; + } + } + + private async queueBroadcast(data: IRequestJob) { + const broadcast = await this.processBroadcast(data.content as BroadcastDto, data.dsnpUserId); + await this.broadcastQueue.add(`Broadcast Job - ${data.id}`, broadcast, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); + } + + private async queueReply(data: IRequestJob) { + const reply = await this.processReply(data.content as ReplyDto, data.dsnpUserId); + await this.replyQueue.add(`Reply Job - ${data.id}`, reply, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); + } + + private async queueReaction(data: IRequestJob) { + const reaction = await this.processReaction(data.content as ReactionDto, data.dsnpUserId); + await this.reactionQueue.add(`Reaction Job - ${data.id}`, reaction, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); + } + + private async queueUpdate(data: IRequestJob) { + const updateDto = data.content as UpdateDto; + const updateAnnouncementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(updateDto.targetAnnouncementType); + const update = await this.processUpdate(updateDto, updateAnnouncementType, updateDto.targetContentHash ?? '', data.dsnpUserId); + await this.updateQueue.add(`Update Job - ${data.id}`, update, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); + } + + private async queueProfile(data: IRequestJob) { + const profile = await this.processProfile(data.content as ProfileDto, data.dsnpUserId); + await this.profileQueue.add(`Profile Job - ${data.id}`, profile, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); + } + + private async queueTombstone(data: IRequestJob) { + const tombStoneDto = data.content as TombstoneDto; + const announcementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(tombStoneDto.targetAnnouncementType); + const tombstone = createTombstone(data.dsnpUserId, announcementType, tombStoneDto.targetContentHash ?? ''); + await this.tombstoneQueue.add(`Tombstone Job - ${data.id}`, tombstone, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); + } + + private async getAnnouncementTypeFromModifiableAnnouncementType(modifiableAnnouncementType: ModifiableAnnouncementTypeDto): Promise { + this.logger.debug(`Getting announcement type from modifiable announcement type`); + switch (modifiableAnnouncementType) { + case ModifiableAnnouncementTypeDto.BROADCAST: + return AnnouncementType.Broadcast; + case ModifiableAnnouncementTypeDto.REPLY: + return AnnouncementType.Reply; + default: + throw new Error(`Unsupported announcement type ${typeof modifiableAnnouncementType}`); + } + } + + public async prepareNote(noteContent?: any): Promise<[string, string, string]> { + this.logger.debug(`Preparing note`); + const tags: ActivityContentTag[] = this.prepareTags(noteContent?.content.tag); + const attachments: ActivityContentAttachment[] = await this.prepareAttachments(noteContent?.content.assets); + + const note = createNote(noteContent?.content.content ?? '', new Date(noteContent?.content.published ?? ''), { + name: noteContent?.content.name, + location: this.prepareLocation(noteContent?.content.location), + tag: tags, + attachment: attachments, + }); + const noteString = JSON.stringify(note); + const [cid, hash] = await this.pinBufferToIPFS(Buffer.from(noteString)); + const ipfsUrl = await this.formIpfsUrl(cid); + return [cid, ipfsUrl, hash]; + } + + private prepareTags(tagData?: any[]): ActivityContentTag[] { + this.logger.debug(`Preparing tags`); + const tags: ActivityContentTag[] = []; + if (tagData) { + tagData.forEach((tag) => { + switch (tag.type) { + case TagTypeDto.Hashtag: + tags.push({ name: tag.name }); + break; + case TagTypeDto.Mention: + tags.push({ + name: tag.name, + type: 'Mention', + id: tag.mentionedId, + }); + break; + default: + throw new Error(`Unsupported tag type ${typeof tag.type}`); + } + }); + } + return tags; + } + + private async prepareAttachments(assetData?: any[]): Promise { + const attachments: ActivityContentAttachment[] = []; + if (assetData) { + assetData.forEach(async (asset) => { + switch (asset.type) { + case AttachmentTypeDto.LINK: + attachments.push(this.prepareLinkAttachment(asset)); + break; + case AttachmentTypeDto.IMAGE: + attachments.push(await this.prepareImageAttachment(asset)); + break; + case AttachmentTypeDto.VIDEO: + attachments.push(await this.prepareVideoAttachment(asset)); + break; + case AttachmentTypeDto.AUDIO: + attachments.push(await this.prepareAudioAttachment(asset)); + break; + default: + throw new Error(`Unsupported attachment type ${typeof asset.type}`); + } + }); + } + + return attachments; + } + + private prepareLinkAttachment(asset: AssetDto): ActivityContentLink { + this.logger.debug(`Preparing link attachment`); + return { + type: 'Link', + href: asset.href || '', + name: asset.name, + }; + } + + private async prepareImageAttachment(asset: AssetDto): Promise { + const imageLinks: ActivityContentImageLink[] = []; + asset.references?.forEach(async (reference) => { + const assetMetaData = await this.assetQueue.getJob(reference.referenceId); + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + const image: ActivityContentImageLink = { + mediaType: assetMetaData?.data.mimeType, + hash: [hashedContent], + height: reference.height, + width: reference.width, + type: 'Link', + href: await this.formIpfsUrl(reference.referenceId), + }; + imageLinks.push(image); + }); + + return { + type: 'Image', + name: asset.name, + url: imageLinks, + }; + } + + private async prepareVideoAttachment(asset: AssetDto): Promise { + const videoLinks: ActivityContentVideoLink[] = []; + let duration = ''; + asset.references?.forEach(async (reference) => { + const assetMetaData = await this.assetQueue.getJob(reference.referenceId); + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + const video: ActivityContentVideoLink = { + mediaType: assetMetaData?.data.mimeType, + hash: [hashedContent], + height: reference.height, + width: reference.width, + type: 'Link', + href: await this.formIpfsUrl(reference.referenceId), + }; + duration = duration ?? reference.duration ?? ''; + videoLinks.push(video); + }); + + return { + type: 'Video', + name: asset.name, + url: videoLinks, + duration, + }; + } + + private async prepareAudioAttachment(asset: AssetDto): Promise { + const audioLinks: ActivityContentAudioLink[] = []; + let duration = ''; + asset.references?.forEach(async (reference) => { + const assetMetaData = await this.assetQueue.getJob(reference.referenceId); + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + duration = duration ?? reference.duration ?? ''; + const audio: ActivityContentAudioLink = { + mediaType: assetMetaData?.data.mimeType, + hash: [hashedContent], + type: 'Link', + href: await this.formIpfsUrl(reference.referenceId), + }; + audioLinks.push(audio); + }); + + return { + type: 'Audio', + name: asset.name, + url: audioLinks, + duration, + }; + } + + private async processBroadcast(content: BroadcastDto, dsnpUserId: string): Promise { + this.logger.debug(`Processing broadcast`); + const [cid, ipfsUrl, hash] = await this.prepareNote(content); + return createBroadcast(dsnpUserId, ipfsUrl, hash); + } + + private async processReply(content: ReplyDto, dsnpUserId: string): Promise { + this.logger.debug(`Processing reply for ${content.inReplyTo}`); + const [cid, ipfsUrl, hash] = await this.prepareNote(content); + return createReply(dsnpUserId, ipfsUrl, hash, content.inReplyTo); + } + + private async processReaction(content: ReactionDto, dsnpUserId: string): Promise { + this.logger.debug(`Processing reaction ${content.emoji} for ${content.inReplyTo}`); + return createReaction(dsnpUserId, content.emoji, content.inReplyTo); + } + + private async processUpdate(content: UpdateDto, targetAnnouncementType: AnnouncementType, targetContentHash: string, dsnpUserId: string): Promise { + this.logger.debug(`Processing update`); + const [cid, ipfsUrl, hash] = await this.prepareNote(content); + return createUpdate(dsnpUserId, ipfsUrl, hash, targetAnnouncementType, targetContentHash); + } + + private async processProfile(content: ProfileDto, dsnpUserId: string): Promise { + this.logger.debug(`Processing profile`); + const attachments: ActivityContentImageLink[] = await this.prepareProfileIconAttachments(content.profile.icon ?? []); + + const profileActivity: ActivityContentProfile = { + '@context': 'https://www.w3.org/ns/activitystreams', + type: 'Profile', + name: content.profile.name, + published: content.profile.published, + location: this.prepareLocation(content.profile.location), + summary: content.profile.summary, + icon: attachments, + tag: this.prepareTags(content.profile.tag), + }; + const profileString = JSON.stringify(profileActivity); + const [cid, hash] = await this.pinBufferToIPFS(Buffer.from(profileString)); + return createProfile(dsnpUserId, await this.formIpfsUrl(cid), hash); + } + + private async prepareProfileIconAttachments(icons: any[]): Promise { + const attachments: ActivityContentImageLink[] = []; + icons.forEach(async (icon) => { + const assetMetaData = await this.assetQueue.getJob(icon.referenceId); + const contentBuffer = await this.ipfsService.getPinned(icon.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + const image: ActivityContentImageLink = { + mediaType: assetMetaData?.data.mimeType, + hash: [hashedContent], + height: icon.height, + width: icon.width, + type: 'Link', + href: await this.formIpfsUrl(icon.referenceId), + }; + attachments.push(image); + }); + + return attachments; + } + + private prepareLocation(locationData: any): any { + this.logger.debug(`Preparing location`); + if (!locationData) return null; + return { + latitude: locationData.latitude, + longitude: locationData.longitude, + radius: locationData.radius, + altitude: locationData.altitude, + accuracy: locationData.accuracy, + name: locationData.name, + units: locationData.units, + type: 'Place', + }; + } + + private async pinBufferToIPFS(buf: Buffer): Promise<[string, string, number]> { + const { cid, hash, size } = await this.ipfsService.ipfsPin('application/octet-stream', buf); + return [cid.toString(), hash, size]; + } + + private async formIpfsUrl(cid: string): Promise { + return this.configService.getIpfsCidPlaceholder(cid); + } +} diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts index e50fdff1..1795abaa 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts @@ -10,6 +10,7 @@ import { ConfigService } from '../../../api/src/config/config.service'; import { QueueConstants } from '../../../../libs/common/src'; import { RequestProcessorService } from './request.processor.service'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; +import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; @Module({ imports: [ @@ -53,8 +54,26 @@ import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; BullModule.registerQueue({ name: QueueConstants.REQUEST_QUEUE_NAME, }), + BullModule.registerQueue({ + name: QueueConstants.BROADCAST_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.REPLY_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.REACTION_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.TOMBSTONE_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.UPDATE_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.PROFILE_QUEUE_NAME, + }), ], - providers: [RequestProcessorService, IpfsService], - exports: [BullModule, RequestProcessorService, IpfsService], + providers: [RequestProcessorService, IpfsService, DsnpAnnouncementProcessor], + exports: [BullModule, RequestProcessorService, IpfsService, DsnpAnnouncementProcessor], }) export class RequestProcessorModule {} diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts index 1bf598d0..58c15f2d 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts @@ -1,11 +1,12 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; import { Injectable, Logger } from '@nestjs/common'; -import { DelayedError, Job } from 'bullmq'; +import { DelayedError, Job, Queue } from 'bullmq'; import Redis from 'ioredis'; import { ConfigService } from '../../../api/src/config/config.service'; import { AnnouncementTypeDto, BroadcastDto, IRequestJob, ProfileDto, QueueConstants, ReplyDto, UpdateDto } from '../../../../libs/common/src'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; +import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; @Injectable() @Processor(QueueConstants.REQUEST_QUEUE_NAME) @@ -13,7 +14,8 @@ export class RequestProcessorService extends WorkerHost { private logger: Logger; constructor( - @InjectRedis() private redis: Redis, + @InjectRedis() private cacheManager: Redis, + private dsnpAnnouncementProcessor: DsnpAnnouncementProcessor, private configService: ConfigService, private ipfsService: IpfsService, ) { @@ -24,15 +26,19 @@ export class RequestProcessorService extends WorkerHost { async process(job: Job): Promise { this.logger.log(`Processing job ${job.id} of type ${job.name}`); this.logger.debug(job.asJSON()); - const assets = this.getAssetReferencesFromRequestJob(job.data); - - const pinnedAssets = assets.map((cid) => this.ipfsService.getPinned(cid)); - const pinnedResult = await Promise.all(pinnedAssets); - // if any of assets does not exists delay the job for a future attempt - if (pinnedResult.some((buffer) => !buffer)) { - await this.delayJobAndIncrementAttempts(job); - } else { - // TODO: create attachments from assets + try { + const assets = this.getAssetReferencesFromRequestJob(job.data); + const pinnedAssets = assets.map((cid) => this.ipfsService.getPinned(cid)); + const pinnedResult = await Promise.all(pinnedAssets); + // if any of assets does not exists delay the job for a future attempt + if (pinnedResult.some((buffer) => !buffer)) { + await this.delayJobAndIncrementAttempts(job); + } else { + await this.dsnpAnnouncementProcessor.collectAnnouncementAndQueue(job.data); + } + } catch (e) { + this.logger.error(e); + throw e; } } diff --git a/services/content-watcher/libs/common/src/interfaces/dsnp.ts b/services/content-watcher/libs/common/src/interfaces/dsnp.ts index 5b0170d8..9f8664ec 100644 --- a/services/content-watcher/libs/common/src/interfaces/dsnp.ts +++ b/services/content-watcher/libs/common/src/interfaces/dsnp.ts @@ -18,7 +18,7 @@ export enum AnnouncementType { type TombstoneFields = { announcementType: AnnouncementType.Tombstone; targetAnnouncementType: AnnouncementType; - targetSignature: string; + targetContentHash: string; }; type BroadcastFields = { @@ -46,13 +46,20 @@ type ProfileFields = { url: string; }; +type UpdateFields = { + announcementType: AnnouncementType.Update; + targetAnnouncementType: AnnouncementType; + targetContentHash: string; + url: string; +}; + /** * TypedAnnouncement: an Announcement with a particular AnnouncementType */ export type TypedAnnouncement = { announcementType: T; fromId: string; -} & (TombstoneFields | BroadcastFields | ReplyFields | ReactionFields | ProfileFields); +} & (TombstoneFields | BroadcastFields | ReplyFields | ReactionFields | ProfileFields | UpdateFields); /** * Announcement: an Announcement intended for inclusion in a batch file @@ -84,6 +91,11 @@ export type ReplyAnnouncement = TypedAnnouncement; */ export type ReactionAnnouncement = TypedAnnouncement; +/** + * UpdateAnnouncement: an Announcement of type Update + */ +export type UpdateAnnouncement = TypedAnnouncement; + /** * createTombstone() generates a tombstone announcement from a given URL and * hash. @@ -93,10 +105,10 @@ export type ReactionAnnouncement = TypedAnnouncement; * @param targetSignature - The signature of the target announcement * @returns A TombstoneAnnouncement */ -export const createTombstone = (fromId: string, targetType: AnnouncementType, targetSignature: string): TombstoneAnnouncement => ({ +export const createTombstone = (fromId: string, targetType: AnnouncementType, targetContentHash: string): TombstoneAnnouncement => ({ announcementType: AnnouncementType.Tombstone, targetAnnouncementType: targetType, - targetSignature, + targetContentHash, fromId, }); @@ -181,3 +193,25 @@ export const createNote = (content: string, published: Date, options?: Partial ({ + announcementType: AnnouncementType.Update, + fromId, + targetAnnouncementType: targetType, + targetContentHash: targetHash, + url, +}); diff --git a/services/content-watcher/libs/common/src/utils/dsnpTypeConverter.ts b/services/content-watcher/libs/common/src/utils/dsnpTypeConverter.ts deleted file mode 100644 index 014c70dd..00000000 --- a/services/content-watcher/libs/common/src/utils/dsnpTypeConverter.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { - ActivityContentTag, - ActivityContentAttachment, - ActivityContentLink, - ActivityContentImageLink, - ActivityContentImage, - ActivityContentVideoLink, - ActivityContentVideo, - ActivityContentAudioLink, - ActivityContentAudio, -} from '@dsnp/activity-content/types'; -import { TagTypeDto, AssetDto, AttachmentTypeDto } from '../dtos/activity.dto'; -import { createNote } from '../interfaces/dsnp'; -import { calculateDsnpHash } from './ipfs'; -import { IpfsService } from './ipfs.client'; -import { ConfigService } from '../../../../apps/api/src/config/config.service'; - -@Injectable() -export class BatchAnnouncer { - private logger: Logger; - - constructor( - private configService: ConfigService, - private ipfsService: IpfsService, - ) { - this.logger = new Logger(BatchAnnouncer.name); - } - - public async prepareNote(noteContent?: any): Promise<[string, string, string]> { - this.logger.debug(`Preparing note`); - const tags: ActivityContentTag[] = []; - if (noteContent?.content.tag) { - noteContent.content.tag.forEach((tag) => { - switch (tag.type) { - case TagTypeDto.Hashtag: - tags.push({ name: tag.name || '' }); - break; - case TagTypeDto.Mention: - tags.push({ - name: tag.name || '', - type: 'Mention', - id: tag.mentionedId || '', - }); - break; - default: - throw new Error(`Unsupported tag type ${typeof tag.type}`); - } - }); - } - - const attachments: ActivityContentAttachment[] = []; - if (noteContent?.content.assets) { - noteContent.content.assets.forEach(async (asset: AssetDto) => { - switch (asset.type) { - case AttachmentTypeDto.LINK: { - const link: ActivityContentLink = { - type: 'Link', - href: asset.href || '', - name: asset.name || '', - }; - - attachments.push(link); - break; - } - case AttachmentTypeDto.IMAGE: { - const imageLinks: ActivityContentImageLink[] = []; - asset.references?.forEach(async (reference) => { - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - const image: ActivityContentImageLink = { - mediaType: 'image', // TODO - hash: [hashedContent], - height: reference.height, - width: reference.width, - type: 'Link', - href: await this.formIpfsUrl(reference.referenceId), - }; - imageLinks.push(image); - }); - const imageActivity: ActivityContentImage = { - type: 'Image', - name: asset.name || '', - url: imageLinks, - }; - - attachments.push(imageActivity); - break; - } - case AttachmentTypeDto.VIDEO: { - const videoLinks: ActivityContentVideoLink[] = []; - let duration = ''; - asset.references?.forEach(async (reference) => { - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - const video: ActivityContentVideoLink = { - mediaType: 'video', // TODO - hash: [hashedContent], - height: reference.height, - width: reference.width, - type: 'Link', - href: await this.formIpfsUrl(reference.referenceId), - }; - duration = reference.duration ?? ''; - videoLinks.push(video); - }); - const videoActivity: ActivityContentVideo = { - type: 'Video', - name: asset.name || '', - url: videoLinks, - duration, - }; - - attachments.push(videoActivity); - break; - } - case AttachmentTypeDto.AUDIO: { - const audioLinks: ActivityContentAudioLink[] = []; - let duration = ''; - asset.references?.forEach(async (reference) => { - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - duration = reference.duration ?? ''; - const audio: ActivityContentAudioLink = { - mediaType: 'audio', // TODO - hash: [hashedContent], - type: 'Link', - href: await this.formIpfsUrl(reference.referenceId), - }; - audioLinks.push(audio); - }); - const audioActivity: ActivityContentAudio = { - type: 'Audio', - name: asset.name || '', - url: audioLinks, - duration, - }; - - attachments.push(audioActivity); - break; - } - default: - throw new Error(`Unsupported attachment type ${typeof asset.type}`); - } - }); - } - - const note = createNote(noteContent?.content.content ?? '', new Date(noteContent?.content.published ?? ''), { - name: noteContent?.content.name, - location: { - latitude: noteContent?.content.location?.latitude, - longitude: noteContent?.content.location?.longitude, - radius: noteContent?.content.location?.radius, - altitude: noteContent?.content.location?.altitude, - accuracy: noteContent?.content.location?.accuracy, - name: noteContent?.content.location?.name || '', - type: 'Place', - }, - tag: tags, - attachment: attachments, - }); - const noteString = JSON.stringify(note); - const [cid, hash] = await this.pinStringToIPFS(Buffer.from(noteString)); - const ipfsUrl = await this.formIpfsUrl(cid); - return [cid, hash, ipfsUrl]; - } - - private async pinStringToIPFS(buf: Buffer): Promise<[string, string]> { - const { cid, size } = await this.ipfsService.ipfsPin('application/octet-stream', buf); - return [cid.toString(), size.toString()]; - } - - private async formIpfsUrl(cid: string): Promise { - return this.configService.getIpfsCidPlaceholder(cid); - } -} From 967e4b2b02394c8410677626ef1655c9f66a446c Mon Sep 17 00:00:00 2001 From: Aramik Date: Fri, 8 Sep 2023 17:25:55 -0700 Subject: [PATCH 018/137] fixed metadata dependency (#40) --- .../apps/api/src/api.controller.ts | 16 +- .../apps/api/src/api.service.ts | 27 ++- .../dsnp.announcement.processor.spec.ts | 6 + .../dsnp.announcement.processor.ts | 187 ++++++++++-------- .../request.processor.service.ts | 22 +-- .../libs/common/src/dtos/announcement.dto.ts | 2 +- .../libs/common/src/dtos/validation.dto.ts | 13 ++ .../src/interfaces/request-job.interface.ts | 1 + .../libs/common/src/utils/ipfs.client.ts | 2 +- 9 files changed, 160 insertions(+), 116 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index c06586b0..05f9b138 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -66,15 +66,15 @@ export class ApiController { @Post('content/:userDsnpId/broadcast') @HttpCode(202) async broadcast(@Param() userDsnpId: DsnpUserIdParam, @Body() broadcastDto: BroadcastDto): Promise { - await this.apiService.validateAssets(broadcastDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.BROADCAST, userDsnpId.userDsnpId, broadcastDto); + const metadata = await this.apiService.validateAssetsAndFetchMetadata(broadcastDto as AssetIncludedRequestDto); + return this.apiService.enqueueRequest(AnnouncementTypeDto.BROADCAST, userDsnpId.userDsnpId, broadcastDto, metadata); } @Post('content/:userDsnpId/reply') @HttpCode(202) async reply(@Param() userDsnpId: DsnpUserIdParam, @Body() replyDto: ReplyDto): Promise { - await this.apiService.validateAssets(replyDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.REPLY, userDsnpId.userDsnpId, replyDto); + const metadata = await this.apiService.validateAssetsAndFetchMetadata(replyDto as AssetIncludedRequestDto); + return this.apiService.enqueueRequest(AnnouncementTypeDto.REPLY, userDsnpId.userDsnpId, replyDto, metadata); } @Post('content/:userDsnpId/reaction') @@ -86,8 +86,8 @@ export class ApiController { @Put('content/:userDsnpId') @HttpCode(202) async update(@Param() userDsnpId: DsnpUserIdParam, @Body() updateDto: UpdateDto): Promise { - await this.apiService.validateAssets(updateDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.UPDATE, userDsnpId.userDsnpId, updateDto); + const metadata = await this.apiService.validateAssetsAndFetchMetadata(updateDto as AssetIncludedRequestDto); + return this.apiService.enqueueRequest(AnnouncementTypeDto.UPDATE, userDsnpId.userDsnpId, updateDto, metadata); } @Delete('content/:userDsnpId') @@ -99,7 +99,7 @@ export class ApiController { @Put('profile/:userDsnpId') @HttpCode(202) async profile(@Param() userDsnpId: DsnpUserIdParam, @Body() profileDto: ProfileDto): Promise { - await this.apiService.validateAssets(profileDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.PROFILE, userDsnpId.userDsnpId, profileDto); + const metadata = await this.apiService.validateAssetsAndFetchMetadata(profileDto as AssetIncludedRequestDto); + return this.apiService.enqueueRequest(AnnouncementTypeDto.PROFILE, userDsnpId.userDsnpId, profileDto, metadata); } } diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index d985757f..b996be87 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -16,7 +16,7 @@ import { RequestTypeDto, UploadResponseDto, } from '../../../libs/common/src'; -import { calculateDsnpHash, calculateIpfsCID } from '../../../libs/common/src/utils/ipfs'; +import { calculateIpfsCID } from '../../../libs/common/src/utils/ipfs'; import { IAssetJob, IAssetMetadata } from '../../../libs/common/src/interfaces/asset-job.interface'; import { RedisUtils } from '../../../libs/common/src/utils/redis'; import getAssetDataKey = RedisUtils.getAssetDataKey; @@ -34,7 +34,12 @@ export class ApiService { this.logger = new Logger(this.constructor.name); } - async enqueueRequest(announcementType: AnnouncementTypeDto, dsnpUserId: string, content?: RequestTypeDto): Promise { + async enqueueRequest( + announcementType: AnnouncementTypeDto, + dsnpUserId: string, + content: RequestTypeDto, + assetToMimeType?: Map, + ): Promise { const data = { content, id: '', @@ -43,6 +48,10 @@ export class ApiService { dependencyAttempt: 0, } as IRequestJob; data.id = this.calculateJobId(data); + if (assetToMimeType) { + // not used in id calculation since the order in map might not be deterministic + data.assetToMimeType = assetToMimeType; + } const job = await this.requestQueue.add(`Request Job - ${data.id}`, data, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); // TODO: should come from config this.logger.debug(job); return { @@ -50,7 +59,7 @@ export class ApiService { }; } - async validateAssets(content: AssetIncludedRequestDto): Promise { + async validateAssetsAndFetchMetadata(content: AssetIncludedRequestDto): Promise | undefined> { const checkingList: Array<{ onlyImage: boolean; referenceId: string }> = []; if (content.profile) { content.profile.icon?.forEach((reference) => checkingList.push({ onlyImage: true, referenceId: reference.referenceId })); @@ -68,13 +77,16 @@ export class ApiService { const redisResults = await Promise.all(checkingList.map((obj) => this.redis.get(getAssetMetadataKey(obj.referenceId)))); const errors: string[] = []; + const map = new Map(); redisResults.forEach((res, index) => { if (res === null) { errors.push(`${content.profile ? 'profile.icon' : 'content.assets'}.referenceId ${checkingList[index].referenceId} does not exist!`); - } else if (checkingList[index].onlyImage) { - // checks if attached asset is an image + } else { const metadata: IAssetMetadata = JSON.parse(res); - if (!isImage(metadata.mimeType)) { + map[checkingList[index].referenceId] = metadata.mimeType; + + // checks if attached asset is an image + if (checkingList[index].onlyImage && !isImage(metadata.mimeType)) { errors.push(`profile.icon.referenceId ${checkingList[index].referenceId} is not an image!`); } } @@ -82,6 +94,7 @@ export class ApiService { if (errors.length > 0) { throw new HttpErrorByCode[400](errors); } + return map; } // TODO: make all these operations transactional @@ -142,7 +155,7 @@ export class ApiService { } // eslint-disable-next-line class-methods-use-this - calculateJobId(jobWithoutId: IRequestJob): string { + private calculateJobId(jobWithoutId: IRequestJob): string { const stringVal = JSON.stringify(jobWithoutId); return createHash('sha1').update(stringVal).digest('base64url'); } diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts index 42119ad1..8e15b53a 100644 --- a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts +++ b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts @@ -66,6 +66,7 @@ describe('DsnpAnnouncementProcessor', () => { published: '2021-01-01T00:00:00.000Z', }, }, + assetToMimeType: new Map(), }; await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); @@ -91,6 +92,7 @@ describe('DsnpAnnouncementProcessor', () => { }, inReplyTo: 'dsnp://123/reply/1', }, + assetToMimeType: new Map(), }; await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); @@ -115,6 +117,7 @@ describe('DsnpAnnouncementProcessor', () => { inReplyTo: 'dsnp://123/reply/1', apply: 10, }, + assetToMimeType: new Map(), }; await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); @@ -141,6 +144,7 @@ describe('DsnpAnnouncementProcessor', () => { targetAnnouncementType: ModifiableAnnouncementTypeDto.REPLY, targetContentHash: 'dsnp://123/reply/1', }, + assetToMimeType: new Map(), }; await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); @@ -171,6 +175,7 @@ describe('DsnpAnnouncementProcessor', () => { ], }, }, + assetToMimeType: new Map(), }; await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); @@ -194,6 +199,7 @@ describe('DsnpAnnouncementProcessor', () => { targetAnnouncementType: ModifiableAnnouncementTypeDto.BROADCAST, targetContentHash: 'dsnp://123/broadcast/1', }, + assetToMimeType: new Map(), }; await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts index cd0952f0..59990c12 100644 --- a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts +++ b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts @@ -52,7 +52,6 @@ export class DsnpAnnouncementProcessor { private logger: Logger; constructor( - @InjectQueue(QueueConstants.ASSET_QUEUE_NAME) private assetQueue: Queue, @InjectQueue(QueueConstants.BROADCAST_QUEUE_NAME) private broadcastQueue: Queue, @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, @InjectQueue(QueueConstants.REACTION_QUEUE_NAME) private reactionQueue: Queue, @@ -98,12 +97,12 @@ export class DsnpAnnouncementProcessor { } private async queueBroadcast(data: IRequestJob) { - const broadcast = await this.processBroadcast(data.content as BroadcastDto, data.dsnpUserId); + const broadcast = await this.processBroadcast(data.content as BroadcastDto, data.dsnpUserId, data.assetToMimeType); await this.broadcastQueue.add(`Broadcast Job - ${data.id}`, broadcast, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); } private async queueReply(data: IRequestJob) { - const reply = await this.processReply(data.content as ReplyDto, data.dsnpUserId); + const reply = await this.processReply(data.content as ReplyDto, data.dsnpUserId, data.assetToMimeType); await this.replyQueue.add(`Reply Job - ${data.id}`, reply, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); } @@ -115,12 +114,12 @@ export class DsnpAnnouncementProcessor { private async queueUpdate(data: IRequestJob) { const updateDto = data.content as UpdateDto; const updateAnnouncementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(updateDto.targetAnnouncementType); - const update = await this.processUpdate(updateDto, updateAnnouncementType, updateDto.targetContentHash ?? '', data.dsnpUserId); + const update = await this.processUpdate(updateDto, updateAnnouncementType, updateDto.targetContentHash ?? '', data.dsnpUserId, data.assetToMimeType); await this.updateQueue.add(`Update Job - ${data.id}`, update, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); } private async queueProfile(data: IRequestJob) { - const profile = await this.processProfile(data.content as ProfileDto, data.dsnpUserId); + const profile = await this.processProfile(data.content as ProfileDto, data.dsnpUserId, data.assetToMimeType); await this.profileQueue.add(`Profile Job - ${data.id}`, profile, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); } @@ -143,20 +142,20 @@ export class DsnpAnnouncementProcessor { } } - public async prepareNote(noteContent?: any): Promise<[string, string, string]> { + public async prepareNote(noteContent: any, assetToMimeType?: Map): Promise<[string, string, string]> { this.logger.debug(`Preparing note`); const tags: ActivityContentTag[] = this.prepareTags(noteContent?.content.tag); - const attachments: ActivityContentAttachment[] = await this.prepareAttachments(noteContent?.content.assets); + const attachments: ActivityContentAttachment[] = await this.prepareAttachments(noteContent.content.assets, assetToMimeType); - const note = createNote(noteContent?.content.content ?? '', new Date(noteContent?.content.published ?? ''), { - name: noteContent?.content.name, - location: this.prepareLocation(noteContent?.content.location), + const note = createNote(noteContent.content.content ?? '', new Date(noteContent.content.published ?? ''), { + name: noteContent.content.name, + location: this.prepareLocation(noteContent.content.location), tag: tags, attachment: attachments, }); const noteString = JSON.stringify(note); const [cid, hash] = await this.pinBufferToIPFS(Buffer.from(noteString)); - const ipfsUrl = await this.formIpfsUrl(cid); + const ipfsUrl = this.formIpfsUrl(cid); return [cid, ipfsUrl, hash]; } @@ -184,27 +183,28 @@ export class DsnpAnnouncementProcessor { return tags; } - private async prepareAttachments(assetData?: any[]): Promise { + private async prepareAttachments(assetData?: any[], assetToMimeType?: Map): Promise { const attachments: ActivityContentAttachment[] = []; if (assetData) { - assetData.forEach(async (asset) => { + const promises = assetData.map(async (asset) => { switch (asset.type) { case AttachmentTypeDto.LINK: attachments.push(this.prepareLinkAttachment(asset)); break; case AttachmentTypeDto.IMAGE: - attachments.push(await this.prepareImageAttachment(asset)); + attachments.push(await this.prepareImageAttachment(asset, assetToMimeType)); break; case AttachmentTypeDto.VIDEO: - attachments.push(await this.prepareVideoAttachment(asset)); + attachments.push(await this.prepareVideoAttachment(asset, assetToMimeType)); break; case AttachmentTypeDto.AUDIO: - attachments.push(await this.prepareAudioAttachment(asset)); + attachments.push(await this.prepareAudioAttachment(asset, assetToMimeType)); break; default: throw new Error(`Unsupported attachment type ${typeof asset.type}`); } }); + await Promise.all(promises); } return attachments; @@ -219,22 +219,28 @@ export class DsnpAnnouncementProcessor { }; } - private async prepareImageAttachment(asset: AssetDto): Promise { + private async prepareImageAttachment(asset: AssetDto, assetToMimeType?: Map): Promise { const imageLinks: ActivityContentImageLink[] = []; - asset.references?.forEach(async (reference) => { - const assetMetaData = await this.assetQueue.getJob(reference.referenceId); - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - const image: ActivityContentImageLink = { - mediaType: assetMetaData?.data.mimeType, - hash: [hashedContent], - height: reference.height, - width: reference.width, - type: 'Link', - href: await this.formIpfsUrl(reference.referenceId), - }; - imageLinks.push(image); - }); + if (asset.references) { + const promises = asset.references.map(async (reference) => { + if (!assetToMimeType) { + throw new Error(`asset ${reference.referenceId} should have a mimeTypes`); + } + const mediaType = assetToMimeType[reference.referenceId]; + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + const image: ActivityContentImageLink = { + mediaType, + hash: [hashedContent], + height: reference.height, + width: reference.width, + type: 'Link', + href: this.formIpfsUrl(reference.referenceId), + }; + imageLinks.push(image); + }); + await Promise.all(promises); + } return { type: 'Image', @@ -243,49 +249,63 @@ export class DsnpAnnouncementProcessor { }; } - private async prepareVideoAttachment(asset: AssetDto): Promise { + private async prepareVideoAttachment(asset: AssetDto, assetToMimeType?: Map): Promise { const videoLinks: ActivityContentVideoLink[] = []; - let duration = ''; - asset.references?.forEach(async (reference) => { - const assetMetaData = await this.assetQueue.getJob(reference.referenceId); - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - const video: ActivityContentVideoLink = { - mediaType: assetMetaData?.data.mimeType, - hash: [hashedContent], - height: reference.height, - width: reference.width, - type: 'Link', - href: await this.formIpfsUrl(reference.referenceId), - }; - duration = duration ?? reference.duration ?? ''; - videoLinks.push(video); - }); + let duration: string | undefined = ''; + + if (asset.references) { + const promises = asset.references.map(async (reference) => { + if (!assetToMimeType) { + throw new Error(`asset ${reference.referenceId} should have a mimeTypes`); + } + const mediaType = assetToMimeType[reference.referenceId]; + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + const video: ActivityContentVideoLink = { + mediaType, + hash: [hashedContent], + height: reference.height, + width: reference.width, + type: 'Link', + href: this.formIpfsUrl(reference.referenceId), + }; + duration = duration ? reference.duration : ''; + videoLinks.push(video); + }); + await Promise.all(promises); + } return { type: 'Video', name: asset.name, url: videoLinks, - duration, + duration: duration ?? undefined, }; } - private async prepareAudioAttachment(asset: AssetDto): Promise { + private async prepareAudioAttachment(asset: AssetDto, assetToMimeType?: Map): Promise { const audioLinks: ActivityContentAudioLink[] = []; let duration = ''; - asset.references?.forEach(async (reference) => { - const assetMetaData = await this.assetQueue.getJob(reference.referenceId); - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - duration = duration ?? reference.duration ?? ''; - const audio: ActivityContentAudioLink = { - mediaType: assetMetaData?.data.mimeType, - hash: [hashedContent], - type: 'Link', - href: await this.formIpfsUrl(reference.referenceId), - }; - audioLinks.push(audio); - }); + if (asset.references) { + const promises = asset.references.map(async (reference) => { + if (!assetToMimeType) { + throw new Error(`asset ${reference.referenceId} should have a mimeTypes`); + } + const mediaType = assetToMimeType[reference.referenceId]; + const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); + const hashedContent = await calculateDsnpHash(contentBuffer); + duration = duration ?? reference.duration ?? ''; + const audio: ActivityContentAudioLink = { + mediaType, + hash: [hashedContent], + type: 'Link', + href: this.formIpfsUrl(reference.referenceId), + }; + audioLinks.push(audio); + }); + + await Promise.all(promises); + } return { type: 'Audio', @@ -295,15 +315,15 @@ export class DsnpAnnouncementProcessor { }; } - private async processBroadcast(content: BroadcastDto, dsnpUserId: string): Promise { + private async processBroadcast(content: BroadcastDto, dsnpUserId: string, assetToMimeType?: Map): Promise { this.logger.debug(`Processing broadcast`); - const [cid, ipfsUrl, hash] = await this.prepareNote(content); + const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createBroadcast(dsnpUserId, ipfsUrl, hash); } - private async processReply(content: ReplyDto, dsnpUserId: string): Promise { + private async processReply(content: ReplyDto, dsnpUserId: string, assetToMimeType?: Map): Promise { this.logger.debug(`Processing reply for ${content.inReplyTo}`); - const [cid, ipfsUrl, hash] = await this.prepareNote(content); + const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createReply(dsnpUserId, ipfsUrl, hash, content.inReplyTo); } @@ -312,15 +332,21 @@ export class DsnpAnnouncementProcessor { return createReaction(dsnpUserId, content.emoji, content.inReplyTo); } - private async processUpdate(content: UpdateDto, targetAnnouncementType: AnnouncementType, targetContentHash: string, dsnpUserId: string): Promise { + private async processUpdate( + content: UpdateDto, + targetAnnouncementType: AnnouncementType, + targetContentHash: string, + dsnpUserId: string, + assetToMimeType?: Map, + ): Promise { this.logger.debug(`Processing update`); - const [cid, ipfsUrl, hash] = await this.prepareNote(content); + const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createUpdate(dsnpUserId, ipfsUrl, hash, targetAnnouncementType, targetContentHash); } - private async processProfile(content: ProfileDto, dsnpUserId: string): Promise { + private async processProfile(content: ProfileDto, dsnpUserId: string, assetToMimeType?: Map): Promise { this.logger.debug(`Processing profile`); - const attachments: ActivityContentImageLink[] = await this.prepareProfileIconAttachments(content.profile.icon ?? []); + const attachments: ActivityContentImageLink[] = await this.prepareProfileIconAttachments(content.profile.icon ?? [], assetToMimeType); const profileActivity: ActivityContentProfile = { '@context': 'https://www.w3.org/ns/activitystreams', @@ -334,25 +360,30 @@ export class DsnpAnnouncementProcessor { }; const profileString = JSON.stringify(profileActivity); const [cid, hash] = await this.pinBufferToIPFS(Buffer.from(profileString)); - return createProfile(dsnpUserId, await this.formIpfsUrl(cid), hash); + return createProfile(dsnpUserId, this.formIpfsUrl(cid), hash); } - private async prepareProfileIconAttachments(icons: any[]): Promise { + private async prepareProfileIconAttachments(icons: any[], assetToMimeType?: Map): Promise { const attachments: ActivityContentImageLink[] = []; - icons.forEach(async (icon) => { - const assetMetaData = await this.assetQueue.getJob(icon.referenceId); + + const promises = icons.map(async (icon) => { + if (!assetToMimeType) { + throw new Error(`asset ${icon.referenceId} should have a mimeTypes`); + } + const mediaType = assetToMimeType[icon.referenceId]; const contentBuffer = await this.ipfsService.getPinned(icon.referenceId); const hashedContent = await calculateDsnpHash(contentBuffer); const image: ActivityContentImageLink = { - mediaType: assetMetaData?.data.mimeType, + mediaType, hash: [hashedContent], height: icon.height, width: icon.width, type: 'Link', - href: await this.formIpfsUrl(icon.referenceId), + href: this.formIpfsUrl(icon.referenceId), }; attachments.push(image); }); + await Promise.all(promises); return attachments; } @@ -377,7 +408,7 @@ export class DsnpAnnouncementProcessor { return [cid.toString(), hash, size]; } - private async formIpfsUrl(cid: string): Promise { + private formIpfsUrl(cid: string): string { return this.configService.getIpfsCidPlaceholder(cid); } } diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts index 58c15f2d..1d825752 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts @@ -27,7 +27,7 @@ export class RequestProcessorService extends WorkerHost { this.logger.log(`Processing job ${job.id} of type ${job.name}`); this.logger.debug(job.asJSON()); try { - const assets = this.getAssetReferencesFromRequestJob(job.data); + const assets: string[] = job.data.assetToMimeType ? Object.keys(job.data.assetToMimeType) : []; const pinnedAssets = assets.map((cid) => this.ipfsService.getPinned(cid)); const pinnedResult = await Promise.all(pinnedAssets); // if any of assets does not exists delay the job for a future attempt @@ -46,26 +46,6 @@ export class RequestProcessorService extends WorkerHost { @OnWorkerEvent('completed') onCompleted() {} - // eslint-disable-next-line class-methods-use-this - private getAssetReferencesFromRequestJob(job: IRequestJob): Array { - const assets: string[] = []; - // eslint-disable-next-line default-case - switch (job.announcementType) { - case AnnouncementTypeDto.BROADCAST: - (job.content as BroadcastDto).content.assets?.forEach((a) => a.references?.forEach((r) => assets.push(r.referenceId))); - break; - case AnnouncementTypeDto.REPLY: - (job.content as ReplyDto).content.assets?.forEach((a) => a.references?.forEach((r) => assets.push(r.referenceId))); - break; - case AnnouncementTypeDto.UPDATE: - (job.content as UpdateDto).content.assets?.forEach((a) => a.references?.forEach((r) => assets.push(r.referenceId))); - break; - case AnnouncementTypeDto.PROFILE: - (job.content as ProfileDto).profile.icon?.forEach((r) => assets.push(r.referenceId)); - } - return assets; - } - // eslint-disable-next-line class-methods-use-this private async delayJobAndIncrementAttempts(job: Job) { const { data } = job; diff --git a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts index 7cf3c587..7f990f77 100644 --- a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts @@ -80,4 +80,4 @@ export class ProfileDto { } export type RequestTypeDto = BroadcastDto | ReplyDto | ReactionDto | UpdateDto | ProfileDto | TombstoneDto; -export type AssetIncludedRequestDto = BroadcastDto & ReplyDto & UpdateDto & ProfileDto & TombstoneDto; +export type AssetIncludedRequestDto = BroadcastDto & ReplyDto & UpdateDto & ProfileDto; diff --git a/services/content-watcher/libs/common/src/dtos/validation.dto.ts b/services/content-watcher/libs/common/src/dtos/validation.dto.ts index 9df5b40c..b37cef8c 100644 --- a/services/content-watcher/libs/common/src/dtos/validation.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/validation.dto.ts @@ -1,3 +1,5 @@ +import { AnnouncementTypeDto } from './common.dto'; + /** * Regex for ISO 8601 * - T separation @@ -41,3 +43,14 @@ export const DSNP_VALID_MIME_TYPES = export function isImage(mimeType: string): boolean { return mimeType != null && mimeType.toLowerCase().startsWith('image'); } +/** + * checks to see if the type might have some assets + */ +export function isAssetIncludedType(announcementType: AnnouncementTypeDto): boolean { + return ( + announcementType === AnnouncementTypeDto.PROFILE || + announcementType === AnnouncementTypeDto.BROADCAST || + announcementType === AnnouncementTypeDto.REPLY || + announcementType === AnnouncementTypeDto.UPDATE + ); +} diff --git a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts index fb44bf05..b75b2b7d 100644 --- a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts @@ -5,6 +5,7 @@ export interface IRequestJob { id: string; announcementType: AnnouncementTypeDto; dsnpUserId: string; + assetToMimeType?: Map; content?: RequestTypeDto; dependencyAttempt: number; } diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index 68107c5f..7cc9c320 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -88,7 +88,7 @@ export class IpfsService { authorization: ipfsAuth, }; - const response = await axios.post(ipfsGet, null, { headers, responseType: 'blob' }); + const response = await axios.post(ipfsGet, null, { headers, responseType: 'arraybuffer' }); const { data } = response; return data; From 7a2d6117db5516c8d7006e4d7b069a0c30ee6749 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Fri, 8 Sep 2023 19:27:56 -0500 Subject: [PATCH 019/137] fix extrinsic call param (#41) --- .../content-watcher/apps/worker/src/publisher/ipfs.publisher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts index 31279c76..8260be71 100644 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -38,7 +38,7 @@ export class IPFSPublisher { try { const currrentEpoch = await this.blockchainService.getCurrentCapacityEpoch(); const [txHash, eventMap] = await this.blockchainService - .createExtrinsic({ pallet: 'frequencyTxPayment', extrinsic: 'payWithCapacity' }, { eventPallet: 'messages', event: 'MessagesStored' }, providerKeys, [tx]) + .createExtrinsic({ pallet: 'frequencyTxPayment', extrinsic: 'payWithCapacity' }, { eventPallet: 'messages', event: 'MessagesStored' }, providerKeys, tx) .signAndSend(); if (!txHash) { throw new Error('Tx hash is undefined'); From 22fba41f19a3daf74a1ef14aa717738bd23bac9a Mon Sep 17 00:00:00 2001 From: Aramik Date: Tue, 12 Sep 2023 09:46:34 -0700 Subject: [PATCH 020/137] init batching (#42) * init batching * implemented batching processor * removed extra comments * removed extra comments --- .../apps/api/src/api.module.ts | 20 ++- .../api/src/config/config.service.spec.ts | 11 +- .../apps/api/src/config/config.service.ts | 6 + .../apps/api/src/config/env.config.ts | 13 +- .../apps/api/src/development.controller.ts | 47 ++++++- .../batch_announcer/batch.announcer.spec.ts | 4 +- .../src/batch_announcer/batch.announcer.ts | 34 ++--- .../batching.processor.module.ts | 82 +++++++++++ .../batching.processor.service.ts | 131 ++++++++++++++++++ .../workers/broadcast.worker.ts | 35 +++++ .../workers/profile.worker.ts | 35 +++++ .../workers/reaction.worker.ts | 35 +++++ .../workers/reply.worker.ts | 35 +++++ .../workers/tombstone.worker.ts | 35 +++++ .../workers/update.worker.ts | 35 +++++ .../worker/src/publisher/ipfs.publisher.ts | 3 +- .../apps/worker/src/worker.module.ts | 2 + services/content-watcher/env.template | 6 +- .../libs/common/src/dtos/common.dto.ts | 7 + .../common/src/interfaces/batch.interface.ts | 5 + .../libs/common/src/utils/dsnp.schema.ts | 50 +++++++ .../libs/common/src/utils/redis.ts | 10 ++ services/content-watcher/package-lock.json | 14 +- services/content-watcher/package.json | 2 +- 24 files changed, 611 insertions(+), 46 deletions(-) create mode 100644 services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts create mode 100644 services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts create mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts create mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts create mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts create mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts create mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts create mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts create mode 100644 services/content-watcher/libs/common/src/interfaces/batch.interface.ts create mode 100644 services/content-watcher/libs/common/src/utils/dsnp.schema.ts diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 55b8a956..a7926c67 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -24,6 +24,24 @@ import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; BullModule.registerQueue({ name: QueueConstants.ASSET_QUEUE_NAME, }), + BullModule.registerQueue({ + name: QueueConstants.BROADCAST_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.REPLY_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.REACTION_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.TOMBSTONE_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.UPDATE_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.PROFILE_QUEUE_NAME, + }), ConfigModule, RedisModule.forRootAsync( { @@ -56,7 +74,7 @@ import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; ScheduleModule.forRoot(), ], providers: [ConfigService, ApiService, IpfsService], - controllers: process.env?.ENABLE_DEV_CONTROLLER === 'true' ? [DevelopmentController, ApiController] : [ApiController], + controllers: process.env?.ENVIRONMENT === 'dev' ? [DevelopmentController, ApiController] : [ApiController], exports: [], }) export class ApiModule {} diff --git a/services/content-watcher/apps/api/src/config/config.service.spec.ts b/services/content-watcher/apps/api/src/config/config.service.spec.ts index 549c54c9..f75cb087 100644 --- a/services/content-watcher/apps/api/src/config/config.service.spec.ts +++ b/services/content-watcher/apps/api/src/config/config.service.spec.ts @@ -36,6 +36,7 @@ const setupConfigService = async (envObj: any): Promise => { describe('ContentPublishingConfigService', () => { const ALL_ENV: { [key: string]: string | undefined } = { + ENVIRONMENT: undefined, REDIS_URL: undefined, FREQUENCY_URL: undefined, IPFS_ENDPOINT: undefined, @@ -61,6 +62,11 @@ describe('ContentPublishingConfigService', () => { }); describe('invalid environment', () => { + it('missing environment should fail', async () => { + const { ENVIRONMENT: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ ...env })).rejects.toBeDefined(); + }); + it('missing redis url should fail', async () => { const { REDIS_URL: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ ...env })).rejects.toBeDefined(); @@ -111,11 +117,6 @@ describe('ContentPublishingConfigService', () => { await expect(setupConfigService({ PROVIDER_ACCOUNT_SEED_PHRASE: undefined, ...env })).rejects.toBeDefined(); }); - it('invalid provider account seed phrase should fail', async () => { - const { PROVIDER_ACCOUNT_SEED_PHRASE: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ PROVIDER_ACCOUNT_SEED_PHRASE: 'hello, world', ...env })).rejects.toBeDefined(); - }); - it('invalid webhook failure threshold should fail', async () => { const { WEBHOOK_FAILURE_THRESHOLD: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ WEBHOOK_FAILURE_THRESHOLD: -1, ...env })).rejects.toBeDefined(); diff --git a/services/content-watcher/apps/api/src/config/config.service.ts b/services/content-watcher/apps/api/src/config/config.service.ts index c9df4853..4b9ab223 100644 --- a/services/content-watcher/apps/api/src/config/config.service.ts +++ b/services/content-watcher/apps/api/src/config/config.service.ts @@ -5,8 +5,10 @@ https://docs.nestjs.com/providers#services import { Injectable } from '@nestjs/common'; import { ConfigService as NestConfigService } from '@nestjs/config'; import { ICapacityLimit } from '../interfaces/capacity-limit.interface'; +import { EnvironmentDto } from '../../../../libs/common/src'; export interface ConfigEnvironmentVariables { + ENVIRONMENT: EnvironmentDto; IPFS_ENDPOINT: URL; IPFS_GATEWAY_URL: URL; IPFS_BASIC_AUTH_USER: string; @@ -34,6 +36,10 @@ export class ConfigService { this.capacityLimit = JSON.parse(nestConfigService.get('CAPACITY_LIMIT')!); } + public get environment(): EnvironmentDto { + return this.nestConfigService.get('ENVIRONMENT')!; + } + public get redisUrl(): URL { return this.nestConfigService.get('REDIS_URL')!; } diff --git a/services/content-watcher/apps/api/src/config/env.config.ts b/services/content-watcher/apps/api/src/config/env.config.ts index a6bd6411..2bc980d2 100644 --- a/services/content-watcher/apps/api/src/config/env.config.ts +++ b/services/content-watcher/apps/api/src/config/env.config.ts @@ -1,10 +1,14 @@ import Joi from 'joi'; import { ConfigModuleOptions } from '@nestjs/config'; import { mnemonicValidate } from '@polkadot/util-crypto'; +import { EnvironmentDto } from '../../../../libs/common/src'; export const configModuleOptions: ConfigModuleOptions = { isGlobal: true, validationSchema: Joi.object({ + ENVIRONMENT: Joi.string() + .valid(...Object.values(EnvironmentDto)) + .required(), IPFS_ENDPOINT: Joi.string().uri().required(), IPFS_GATEWAY_URL: Joi.string().required(), // This is parse as string as the required format of this not a valid uri, check .env.template IPFS_BASIC_AUTH_USER: Joi.string().allow('').default(''), @@ -26,14 +30,7 @@ export const configModuleOptions: ConfigModuleOptions = { .min(1) .default(3 * 60), QUEUE_HIGH_WATER: Joi.number().min(100).default(1000), - PROVIDER_ACCOUNT_SEED_PHRASE: Joi.string() - .required() - .custom((value: string, helpers) => { - if (!mnemonicValidate(value)) { - return helpers.error('any.invalid'); - } - return value; - }), + PROVIDER_ACCOUNT_SEED_PHRASE: Joi.string().required(), WEBHOOK_FAILURE_THRESHOLD: Joi.number().min(1).default(3), WEBHOOK_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(10), HEALTH_CHECK_SUCCESS_THRESHOLD: Joi.number().min(1).default(10), diff --git a/services/content-watcher/apps/api/src/development.controller.ts b/services/content-watcher/apps/api/src/development.controller.ts index ff657c4c..bc004dbe 100644 --- a/services/content-watcher/apps/api/src/development.controller.ts +++ b/services/content-watcher/apps/api/src/development.controller.ts @@ -4,21 +4,40 @@ To use it, simply rename and remove the '.dev' extension */ // eslint-disable-next-line max-classes-per-file -import { Controller, Logger, Post, Body, Param, Query, HttpException, HttpStatus, Get } from '@nestjs/common'; +import { Controller, Get, Logger, Param, Post } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; -import { QueueConstants } from '../../../libs/common/src'; +import { Job } from 'bullmq/dist/esm/classes/job'; +import { AnnouncementTypeDto, QueueConstants } from '../../../libs/common/src'; import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; +import { Announcement, AnnouncementType, BroadcastAnnouncement, createBroadcast } from '../../../libs/common/src/interfaces/dsnp'; +import { calculateDsnpHash } from '../../../libs/common/src/utils/ipfs'; @Controller('api/dev') export class DevelopmentController { private readonly logger: Logger; + private readonly queueMapper: Map; + constructor( @InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue, + @InjectQueue(QueueConstants.BROADCAST_QUEUE_NAME) private broadcastQueue: Queue, + @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, + @InjectQueue(QueueConstants.REACTION_QUEUE_NAME) private reactionQueue: Queue, + @InjectQueue(QueueConstants.UPDATE_QUEUE_NAME) private updateQueue: Queue, + @InjectQueue(QueueConstants.PROFILE_QUEUE_NAME) private profileQueue: Queue, + @InjectQueue(QueueConstants.TOMBSTONE_QUEUE_NAME) private tombstoneQueue: Queue, private ipfsService: IpfsService, ) { this.logger = new Logger(this.constructor.name); + this.queueMapper = new Map([ + [AnnouncementTypeDto.BROADCAST, broadcastQueue], + [AnnouncementTypeDto.REPLY, replyQueue], + [AnnouncementTypeDto.REACTION, reactionQueue], + [AnnouncementTypeDto.UPDATE, updateQueue], + [AnnouncementTypeDto.PROFILE, profileQueue], + [AnnouncementTypeDto.TOMBSTONE, tombstoneQueue], + ]); } @Get('/request/:jobId') @@ -41,4 +60,28 @@ export class DevelopmentController { throw error; } } + + @Post('/dummy/announcement/:queueType/:count') + async populate(@Param('queueType') queueType: AnnouncementTypeDto, @Param('count') count: number) { + const promises: Promise[] = []; + // eslint-disable-next-line no-plusplus + for (let i = 0; i < count; i++) { + let data: any; + // eslint-disable-next-line default-case + switch (queueType) { + case AnnouncementTypeDto.BROADCAST: + case AnnouncementTypeDto.PROFILE: + case AnnouncementTypeDto.UPDATE: + case AnnouncementTypeDto.REPLY: + case AnnouncementTypeDto.REACTION: + case AnnouncementTypeDto.TOMBSTONE: + data = createBroadcast(`${Math.floor(Math.random() * 100000000)}`, `https://example.com/${Math.floor(Math.random() * 100000000)}`, '1289739821'); + break; + } + // eslint-disable-next-line no-await-in-loop + const hash = await calculateDsnpHash(Buffer.from(JSON.stringify(data))); + promises.push(this.queueMapper.get(queueType)!.add(`Dummy Job - ${data.id}`, data, { jobId: hash, removeOnFail: false, removeOnComplete: 2000 })); + } + await Promise.all(promises); + } } diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts index 58e022d2..b2127b75 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts @@ -4,6 +4,8 @@ import assert from 'assert'; import { FrequencyParquetSchema } from '@dsnp/frequency-schemas/types/frequency'; // eslint-disable-next-line import/no-extraneous-dependencies import Redis from 'ioredis-mock'; +import { stringToHex } from '@polkadot/util'; +import { Bytes } from '@polkadot/types'; import { BatchAnnouncer } from './batch.announcer'; // Create a mock for the dependencies @@ -72,7 +74,7 @@ describe('BatchAnnouncer', () => { it('should announce a batch to IPFS', async () => { // Mock the necessary dependencies' behavior mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); - mockBlockchainService.getSchema.mockReturnValue({ model: JSON.stringify(broadcast) }); + mockBlockchainService.getSchema.mockReturnValue({ model: Buffer.from(stringToHex(JSON.stringify(broadcast))) }); mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', size: 10, hash: 'mockHash' }); diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts index bbb07956..bb6cb9b2 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts @@ -5,6 +5,7 @@ import { fromFrequencySchema } from '@dsnp/frequency-schemas/parquet'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; import Redis from 'ioredis'; import { PalletSchemasSchema } from '@polkadot/types/lookup'; +import { hexToString } from '@polkadot/util'; import { BlockchainService } from '../blockchain/blockchain.service'; import { ConfigService } from '../../../api/src/config/config.service'; import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; @@ -28,33 +29,32 @@ export class BatchAnnouncer { this.logger.debug(`Announcing batch ${batchJob.batchId} on IPFS`); const { batchId, schemaId, announcements } = batchJob; - let frequencySchema: PalletSchemasSchema; - const schemaCacheKey = `schema:${schemaId}`; - const cachedSchema = await this.cacheManager.get(schemaCacheKey); - if (cachedSchema) { - frequencySchema = JSON.parse(cachedSchema); - } else { - frequencySchema = await this.blockchainService.getSchema(schemaId); - await this.cacheManager.set(schemaCacheKey, JSON.stringify(frequencySchema)); + let cachedSchema: string | null = await this.cacheManager.get(schemaCacheKey); + if (!cachedSchema) { + const schemaResponse = await this.blockchainService.getSchema(schemaId); + cachedSchema = JSON.stringify(schemaResponse); + await this.cacheManager.set(schemaCacheKey, cachedSchema); } - const schema = JSON.parse(frequencySchema.model.toString()); + const frequencySchema: PalletSchemasSchema = JSON.parse(cachedSchema); + const hexString: string = Buffer.from(frequencySchema.model).toString('utf8'); + const schema = JSON.parse(hexToString(hexString)); if (!schema) { throw new Error(`Unable to parse schema for schemaId ${schemaId}`); } const [parquetSchema, writerOptions] = fromFrequencySchema(schema); const publishStream = new PassThrough(); - + const parquetBufferAwait = this.bufferPublishStream(publishStream); const writer = await ParquetWriter.openStream(parquetSchema, publishStream as any, writerOptions); - - announcements.forEach(async (announcement) => { - writer.appendRow(announcement); - }); - + // eslint-disable-next-line no-restricted-syntax + for await (const announcement of announcements) { + await writer.appendRow(announcement); + } await writer.close(); - const buffer = await this.bufferPublishStream(publishStream); + + const buffer = await parquetBufferAwait; const [cid, hash, size] = await this.pinParquetFileToIPFS(buffer); const ipfsUrl = await this.formIpfsUrl(cid); this.logger.debug(`Batch ${batchId} published to IPFS at ${ipfsUrl}`); @@ -64,7 +64,7 @@ export class BatchAnnouncer { private async bufferPublishStream(publishStream: PassThrough): Promise { this.logger.debug('Buffering publish stream'); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const buffers: Buffer[] = []; publishStream.on('data', (data) => { buffers.push(data); diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts new file mode 100644 index 00000000..ed5e3a87 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts @@ -0,0 +1,82 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { BullModule } from '@nestjs/bullmq'; +import { Module } from '@nestjs/common'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { ScheduleModule } from '@nestjs/schedule'; +import { ConfigModule } from '../../../api/src/config/config.module'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { QueueConstants } from '../../../../libs/common/src'; +import { BatchingProcessorService } from './batching.processor.service'; +import { BroadcastWorker } from './workers/broadcast.worker'; +import { ReplyWorker } from './workers/reply.worker'; +import { ReactionWorker } from './workers/reaction.worker'; +import { TombstoneWorker } from './workers/tombstone.worker'; +import { UpdateWorker } from './workers/update.worker'; +import { ProfileWorker } from './workers/profile.worker'; + +@Module({ + imports: [ + ConfigModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + ScheduleModule.forRoot(), + BullModule.registerQueue({ + name: QueueConstants.BATCH_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.BROADCAST_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.REPLY_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.REACTION_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.TOMBSTONE_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.UPDATE_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.PROFILE_QUEUE_NAME, + }), + ], + providers: [BatchingProcessorService, BroadcastWorker, ReplyWorker, ReactionWorker, TombstoneWorker, UpdateWorker, ProfileWorker], + exports: [BullModule, BatchingProcessorService], +}) +export class BatchingProcessorModule {} diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts new file mode 100644 index 00000000..71b54909 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts @@ -0,0 +1,131 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Injectable, Logger } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; +import Redis from 'ioredis'; +import { InjectQueue } from '@nestjs/bullmq'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { randomUUID } from 'crypto'; +import { ConfigService } from '../../../api/src/config/config.service'; +import { Announcement } from '../../../../libs/common/src/interfaces/dsnp'; +import { RedisUtils } from '../../../../libs/common/src/utils/redis'; +import { IBatchMetadata } from '../../../../libs/common/src/interfaces/batch.interface'; +import getBatchMetadataKey = RedisUtils.getBatchMetadataKey; +import getBatchDataKey = RedisUtils.getBatchDataKey; +import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; +import { DsnpSchemas } from '../../../../libs/common/src/utils/dsnp.schema'; +import { QueueConstants } from '../../../../libs/common/src'; + +@Injectable() +export class BatchingProcessorService { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + @InjectQueue(QueueConstants.BATCH_QUEUE_NAME) private outputQueue: Queue, + private schedulerRegistry: SchedulerRegistry, + private configService: ConfigService, + ) { + this.logger = new Logger(this.constructor.name); + } + + async setupActiveBatchTimeout(queueName: string) { + const metadata = await this.getMetadataFromRedis(queueName); + if (metadata) { + const openTimeMs = Math.round(Date.now() - metadata.startTimestamp); + const batchTimeoutInMs = 12 * 1000; // TODO: get from config + if (openTimeMs >= batchTimeoutInMs) { + await this.closeBatch(queueName, metadata.batchId, false); + } else { + const remainingTimeMs = batchTimeoutInMs - openTimeMs; + this.addBatchTimeout(queueName, metadata.batchId, remainingTimeMs); + } + } + } + + async process(job: Job, queueName: string): Promise { + this.logger.log(`Processing job ${job.id} from ${queueName}`); + + const currentBatchMetadata = await this.getMetadataFromRedis(queueName); + if (!currentBatchMetadata) { + this.logger.log(`Processing job ${job.id} no current batch`); + // No active batch exists, creating a new one + const metadata = { + batchId: randomUUID().toString(), + startTimestamp: Date.now(), + rowCount: 1, + } as IBatchMetadata; + const result = await this.redis + .multi() + .set(getBatchMetadataKey(queueName), JSON.stringify(metadata)) + .hsetnx(getBatchDataKey(queueName), job.id!, JSON.stringify(job.data)) + .exec(); + this.logger.debug(result); + + const timeout = 12 * 1000; // TODO: get from config + this.addBatchTimeout(queueName, metadata.batchId, timeout); + } else { + // continue on active batch + this.logger.log(`Processing job ${job.id} existing batch ${currentBatchMetadata.batchId}`); + + currentBatchMetadata.rowCount += 1; + const result = await this.redis + .multi() + .set(getBatchMetadataKey(queueName), JSON.stringify(currentBatchMetadata)) + .hsetnx(getBatchDataKey(queueName), job.id!, JSON.stringify(job.data)) + .exec(); + this.logger.debug(result); + + // TODO: get from config + if (currentBatchMetadata.rowCount >= 100) { + await this.closeBatch(queueName, currentBatchMetadata.batchId, false); + } + } + } + + async onCompleted(job: Job, queueName: string) { + this.logger.log(`Completed ${job.id} from ${queueName}`); + } + + private async closeBatch(queueName: string, batchId: string, timeout: boolean) { + this.logger.log(`Closing batch for ${queueName} ${batchId} ${timeout}`); + const metadata = await this.getMetadataFromRedis(queueName); + const batch = await this.redis.hgetall(getBatchDataKey(queueName)); + const announcements: Announcement[] = []; + Object.keys(batch).forEach((key) => { + const announcement: Announcement = JSON.parse(batch[key]); + announcements.push(announcement); + }); + const job = { + batchId, + schemaId: DsnpSchemas.getSchemaId(this.configService.environment, QueueConstants.QUEUE_NAME_TO_ANNOUNCEMENT_MAP.get(queueName)!), + announcements, + } as IBatchAnnouncerJobData; + await this.outputQueue.add(`Batch Job - ${metadata?.batchId}`, job, { jobId: metadata?.batchId, removeOnFail: false, removeOnComplete: 100 }); + this.logger.debug(batch); + try { + const result = await this.redis.multi().del(getBatchMetadataKey(queueName)).del(getBatchDataKey(queueName)).exec(); + this.logger.debug(result); + const timeoutName = BatchingProcessorService.getTimeoutName(queueName, batchId); + if (this.schedulerRegistry.doesExist('timeout', timeoutName)) { + this.schedulerRegistry.deleteTimeout(timeoutName); + } + } catch (e) { + this.logger.error(e); + } + } + + private async getMetadataFromRedis(queueName: string): Promise { + const batchMetadata = await this.redis.get(getBatchMetadataKey(queueName)); + return batchMetadata ? JSON.parse(batchMetadata) : undefined; + } + + // eslint-disable-next-line class-methods-use-this + private static getTimeoutName(queueName: string, batchId: string): string { + return `TIMEOUT:${queueName}:${batchId}`; + } + + private addBatchTimeout(queueName: string, batchId: string, timeoutMs: number) { + const timeoutHandler = setTimeout(async () => this.closeBatch(queueName, batchId, true), timeoutMs); + this.schedulerRegistry.addTimeout(BatchingProcessorService.getTimeoutName(queueName, batchId), timeoutHandler); + } +} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts new file mode 100644 index 00000000..42c7c056 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts @@ -0,0 +1,35 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Job } from 'bullmq'; +import Redis from 'ioredis'; +import { QueueConstants } from '../../../../../libs/common/src'; +import { BatchingProcessorService } from '../batching.processor.service'; +import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; + +@Injectable() +@Processor(QueueConstants.BROADCAST_QUEUE_NAME) +export class BroadcastWorker extends WorkerHost implements OnApplicationBootstrap { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private batchingProcessorService: BatchingProcessorService, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + async onApplicationBootstrap() { + return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.BROADCAST_QUEUE_NAME); + } + + async process(job: Job): Promise { + return this.batchingProcessorService.process(job, QueueConstants.BROADCAST_QUEUE_NAME); + } + + @OnWorkerEvent('completed') + async onCompleted(job: Job) { + await this.batchingProcessorService.onCompleted(job, QueueConstants.BROADCAST_QUEUE_NAME); + } +} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts new file mode 100644 index 00000000..f1993b20 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts @@ -0,0 +1,35 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Job } from 'bullmq'; +import Redis from 'ioredis'; +import { QueueConstants } from '../../../../../libs/common/src'; +import { BatchingProcessorService } from '../batching.processor.service'; +import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; + +@Injectable() +@Processor(QueueConstants.PROFILE_QUEUE_NAME) +export class ProfileWorker extends WorkerHost implements OnApplicationBootstrap { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private batchingProcessorService: BatchingProcessorService, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + async onApplicationBootstrap() { + return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.PROFILE_QUEUE_NAME); + } + + async process(job: Job): Promise { + return this.batchingProcessorService.process(job, QueueConstants.PROFILE_QUEUE_NAME); + } + + @OnWorkerEvent('completed') + async onCompleted(job: Job) { + await this.batchingProcessorService.onCompleted(job, QueueConstants.PROFILE_QUEUE_NAME); + } +} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts new file mode 100644 index 00000000..ce47207d --- /dev/null +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts @@ -0,0 +1,35 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Job } from 'bullmq'; +import Redis from 'ioredis'; +import { QueueConstants } from '../../../../../libs/common/src'; +import { BatchingProcessorService } from '../batching.processor.service'; +import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; + +@Injectable() +@Processor(QueueConstants.REACTION_QUEUE_NAME) +export class ReactionWorker extends WorkerHost implements OnApplicationBootstrap { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private batchingProcessorService: BatchingProcessorService, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + async onApplicationBootstrap() { + return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.REACTION_QUEUE_NAME); + } + + async process(job: Job): Promise { + return this.batchingProcessorService.process(job, QueueConstants.REACTION_QUEUE_NAME); + } + + @OnWorkerEvent('completed') + async onCompleted(job: Job) { + await this.batchingProcessorService.onCompleted(job, QueueConstants.REACTION_QUEUE_NAME); + } +} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts new file mode 100644 index 00000000..8d292e9d --- /dev/null +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts @@ -0,0 +1,35 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Job } from 'bullmq'; +import Redis from 'ioredis'; +import { QueueConstants } from '../../../../../libs/common/src'; +import { BatchingProcessorService } from '../batching.processor.service'; +import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; + +@Injectable() +@Processor(QueueConstants.REPLY_QUEUE_NAME) +export class ReplyWorker extends WorkerHost implements OnApplicationBootstrap { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private batchingProcessorService: BatchingProcessorService, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + async onApplicationBootstrap() { + return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.REPLY_QUEUE_NAME); + } + + async process(job: Job): Promise { + return this.batchingProcessorService.process(job, QueueConstants.REPLY_QUEUE_NAME); + } + + @OnWorkerEvent('completed') + async onCompleted(job: Job) { + await this.batchingProcessorService.onCompleted(job, QueueConstants.REPLY_QUEUE_NAME); + } +} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts new file mode 100644 index 00000000..a848bd92 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts @@ -0,0 +1,35 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Job } from 'bullmq'; +import Redis from 'ioredis'; +import { QueueConstants } from '../../../../../libs/common/src'; +import { BatchingProcessorService } from '../batching.processor.service'; +import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; + +@Injectable() +@Processor(QueueConstants.TOMBSTONE_QUEUE_NAME) +export class TombstoneWorker extends WorkerHost implements OnApplicationBootstrap { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private batchingProcessorService: BatchingProcessorService, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + async onApplicationBootstrap() { + return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.TOMBSTONE_QUEUE_NAME); + } + + async process(job: Job): Promise { + return this.batchingProcessorService.process(job, QueueConstants.TOMBSTONE_QUEUE_NAME); + } + + @OnWorkerEvent('completed') + async onCompleted(job: Job) { + await this.batchingProcessorService.onCompleted(job, QueueConstants.TOMBSTONE_QUEUE_NAME); + } +} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts new file mode 100644 index 00000000..2d7838d9 --- /dev/null +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts @@ -0,0 +1,35 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Job } from 'bullmq'; +import Redis from 'ioredis'; +import { QueueConstants } from '../../../../../libs/common/src'; +import { BatchingProcessorService } from '../batching.processor.service'; +import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; + +@Injectable() +@Processor(QueueConstants.UPDATE_QUEUE_NAME) +export class UpdateWorker extends WorkerHost implements OnApplicationBootstrap { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private batchingProcessorService: BatchingProcessorService, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + async onApplicationBootstrap() { + return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.UPDATE_QUEUE_NAME); + } + + async process(job: Job): Promise { + return this.batchingProcessorService.process(job, QueueConstants.UPDATE_QUEUE_NAME); + } + + @OnWorkerEvent('completed') + async onCompleted(job: Job) { + await this.batchingProcessorService.onCompleted(job, QueueConstants.UPDATE_QUEUE_NAME); + } +} diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts index 8260be71..3d374169 100644 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -26,9 +26,8 @@ export class IPFSPublisher { } public async publish(message: IPublisherJob): Promise<{ [key: string]: bigint }> { + this.logger.debug(JSON.stringify(message)); const providerKeys = createKeys(this.configService.getProviderAccountSeedPhrase()); - - const batch: SubmittableExtrinsic<'rxjs', ISubmittableResult>[] = []; const tx = this.blockchainService.createExtrinsicCall({ pallet: 'messages', extrinsic: 'addIpfsMessage' }, message.schemaId, message.data.cid, message.data.payloadLength); return this.processSingleBatch(message.id, providerKeys, tx); } diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts index 62ce5776..e10fe63f 100644 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -17,6 +17,7 @@ import { AssetProcessorModule } from './asset_processor/asset.processor.module'; import { AssetProcessorService } from './asset_processor/asset.processor.service'; import { RequestProcessorModule } from './request_processor/request.processor.module'; import { RequestProcessorService } from './request_processor/request.processor.service'; +import { BatchingProcessorModule } from './batching_processor/batching.processor.module'; @Module({ imports: [ @@ -57,6 +58,7 @@ import { RequestProcessorService } from './request_processor/request.processor.s StatusMonitorModule, AssetProcessorModule, RequestProcessorModule, + BatchingProcessorModule, ], providers: [BatchAnnouncementService, ConfigService, WorkerService, PublishingService, StatusMonitoringService, AssetProcessorService, RequestProcessorService], }) diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index 7ce43d61..4127628f 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -8,7 +8,7 @@ PROVIDER_ID=1 REDIS_URL=redis://0.0.0.0:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 -PROVIDER_ACCOUNT_SEED_PHRASE='come finish flower cinnamon blame year glad tank domain hunt release fatigue' +PROVIDER_ACCOUNT_SEED_PHRASE="//Alice" WEBHOOK_FAILURE_THRESHOLD=3 HEALTH_CHECK_SUCCESS_THRESHOLD=10 WEBHOOK_RETRY_INTERVAL_SECONDS=10 @@ -21,5 +21,5 @@ IPFS_BASIC_AUTH_USER="" IPFS_BASIC_AUTH_SECRET="" IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" -# should be enabled for e2e tests -# ENABLE_DEV_CONTROLLER=true \ No newline at end of file +# should be dev for e2e tests +ENVIRONMENT=dev \ No newline at end of file diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index dc1ddadd..ffba7386 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -33,3 +33,10 @@ export enum AnnouncementTypeDto { TOMBSTONE = 'tombstone', PROFILE = 'profile', } + +// eslint-disable-next-line no-shadow +export enum EnvironmentDto { + MAIN_NET = 'mainnet', + ROCOCO = 'rococo', + DEV = 'dev', +} diff --git a/services/content-watcher/libs/common/src/interfaces/batch.interface.ts b/services/content-watcher/libs/common/src/interfaces/batch.interface.ts new file mode 100644 index 00000000..a4b1266a --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/batch.interface.ts @@ -0,0 +1,5 @@ +export interface IBatchMetadata { + batchId: string; + startTimestamp: number; + rowCount: number; +} diff --git a/services/content-watcher/libs/common/src/utils/dsnp.schema.ts b/services/content-watcher/libs/common/src/utils/dsnp.schema.ts new file mode 100644 index 00000000..5af66a73 --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/dsnp.schema.ts @@ -0,0 +1,50 @@ +import { AnnouncementTypeDto, EnvironmentDto } from '../dtos/common.dto'; + +export namespace DsnpSchemas { + /** + * Map between announcement type and it's DSNP schema id for DEV environment + */ + const ANNOUNCEMENT_TO_SCHEMA_ID_DEV = new Map([ + [AnnouncementTypeDto.TOMBSTONE, 1], + [AnnouncementTypeDto.BROADCAST, 2], + [AnnouncementTypeDto.REPLY, 3], + [AnnouncementTypeDto.REACTION, 4], + [AnnouncementTypeDto.PROFILE, 5], + [AnnouncementTypeDto.UPDATE, 6], + ]); + /** + * Map between announcement type and it's DSNP schema id for ROCOCO environment + */ + const ANNOUNCEMENT_TO_SCHEMA_ID_ROCOCO = new Map([ + [AnnouncementTypeDto.TOMBSTONE, 1], + [AnnouncementTypeDto.BROADCAST, 2], + [AnnouncementTypeDto.REPLY, 3], + [AnnouncementTypeDto.REACTION, 4], + [AnnouncementTypeDto.PROFILE, 5], + [AnnouncementTypeDto.UPDATE, 6], + ]); + /** + * Map between announcement type and it's DSNP schema id for MAIN-NET environment + */ + const ANNOUNCEMENT_TO_SCHEMA_ID_MAIN_NET = new Map([ + [AnnouncementTypeDto.TOMBSTONE, 1], + [AnnouncementTypeDto.BROADCAST, 2], + [AnnouncementTypeDto.REPLY, 3], + [AnnouncementTypeDto.REACTION, 4], + [AnnouncementTypeDto.PROFILE, 6], + [AnnouncementTypeDto.UPDATE, 5], + ]); + /** + * Returns schema Id by environment and announcement type + */ + export function getSchemaId(environment: EnvironmentDto, announcementType: AnnouncementTypeDto): number { + switch (environment) { + case EnvironmentDto.MAIN_NET: + return ANNOUNCEMENT_TO_SCHEMA_ID_MAIN_NET.get(announcementType)!; + case EnvironmentDto.ROCOCO: + return ANNOUNCEMENT_TO_SCHEMA_ID_ROCOCO.get(announcementType)!; + default: + return ANNOUNCEMENT_TO_SCHEMA_ID_DEV.get(announcementType)!; + } + } +} diff --git a/services/content-watcher/libs/common/src/utils/redis.ts b/services/content-watcher/libs/common/src/utils/redis.ts index c16f4169..73372d62 100644 --- a/services/content-watcher/libs/common/src/utils/redis.ts +++ b/services/content-watcher/libs/common/src/utils/redis.ts @@ -1,6 +1,8 @@ export namespace RedisUtils { const ASSET_DATA_KEY_PREFIX = 'asset::data'; const ASSET_METADATA_KEY_PREFIX = 'asset::metadata'; + const BATCH_DATA_KEY_PREFIX = 'batch::data'; + const BATCH_METADATA_KEY_PREFIX = 'batch::metadata'; export function getAssetDataKey(assetId: string) { return `${ASSET_DATA_KEY_PREFIX}:${assetId}`; @@ -9,4 +11,12 @@ export namespace RedisUtils { export function getAssetMetadataKey(assetId: string) { return `${ASSET_METADATA_KEY_PREFIX}:${assetId}`; } + + export function getBatchDataKey(queueName: string) { + return `${BATCH_DATA_KEY_PREFIX}:${queueName}`; + } + + export function getBatchMetadataKey(queueName: string) { + return `${BATCH_METADATA_KEY_PREFIX}:${queueName}`; + } } diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 67a5f2f3..9b176ad1 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -24,7 +24,7 @@ "@nestjs/core": "^9.4.0", "@nestjs/event-emitter": "^1.4.1", "@nestjs/platform-express": "^9.4.0", - "@nestjs/schedule": "^3.0.1", + "@nestjs/schedule": "^3.0.3", "@nestjs/swagger": "^7.1.8", "@nestjs/testing": "^9.4.0", "@nestjs/typeorm": "^9.0.1", @@ -1734,10 +1734,11 @@ } }, "node_modules/@nestjs/schedule": { - "version": "3.0.1", - "license": "MIT", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-3.0.3.tgz", + "integrity": "sha512-xsMA4dmP3LcW3rt2iMPfm88bDbCj/hLuDsLrKmJQlbnxyCYtBwLtmu/4cSfZELLM7pTDT+E8QDAqGwhYyUUjxg==", "dependencies": { - "cron": "2.3.1", + "cron": "2.4.1", "uuid": "9.0.0" }, "peerDependencies": { @@ -4935,8 +4936,9 @@ "license": "MIT" }, "node_modules/cron": { - "version": "2.3.1", - "license": "MIT", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.4.1.tgz", + "integrity": "sha512-ty0hUSPuENwDtIShDFxUxWEIsqiu2vhoFtt6Vwrbg4lHGtJX2/cV2p0hH6/qaEM9Pj+i6mQoau48BO5wBpkP4w==", "dependencies": { "luxon": "^3.2.1" } diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 83804757..2f7634d8 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -52,7 +52,7 @@ "@nestjs/core": "^9.4.0", "@nestjs/event-emitter": "^1.4.1", "@nestjs/platform-express": "^9.4.0", - "@nestjs/schedule": "^3.0.1", + "@nestjs/schedule": "^3.0.3", "@nestjs/swagger": "^7.1.8", "@nestjs/testing": "^9.4.0", "@nestjs/typeorm": "^9.0.1", From e7f2712bc07359ba2d9f4808d2216381d00091c6 Mon Sep 17 00:00:00 2001 From: Aramik Date: Tue, 12 Sep 2023 16:36:18 -0700 Subject: [PATCH 021/137] Some minor fixes for activities (#44) --- .../apps/api/src/development.controller.ts | 28 +++++++++++++++---- .../dsnp.announcement.processor.ts | 2 +- .../libs/common/src/interfaces/dsnp.ts | 7 ++++- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/services/content-watcher/apps/api/src/development.controller.ts b/services/content-watcher/apps/api/src/development.controller.ts index bc004dbe..a29bc104 100644 --- a/services/content-watcher/apps/api/src/development.controller.ts +++ b/services/content-watcher/apps/api/src/development.controller.ts @@ -1,6 +1,5 @@ /* This is a controller providing some endpoints useful for development and testing. -To use it, simply rename and remove the '.dev' extension */ // eslint-disable-next-line max-classes-per-file @@ -10,7 +9,7 @@ import { Queue } from 'bullmq'; import { Job } from 'bullmq/dist/esm/classes/job'; import { AnnouncementTypeDto, QueueConstants } from '../../../libs/common/src'; import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; -import { Announcement, AnnouncementType, BroadcastAnnouncement, createBroadcast } from '../../../libs/common/src/interfaces/dsnp'; +import { AnnouncementType, createBroadcast, createProfile, createReaction, createReply, createTombstone, createUpdate } from '../../../libs/common/src/interfaces/dsnp'; import { calculateDsnpHash } from '../../../libs/common/src/utils/ipfs'; @Controller('api/dev') @@ -68,19 +67,38 @@ export class DevelopmentController { for (let i = 0; i < count; i++) { let data: any; // eslint-disable-next-line default-case + const fromId = `${Math.floor(Math.random() * 100000000)}`; + const hash = `${Math.floor(Math.random() * 100000000)}`; switch (queueType) { case AnnouncementTypeDto.BROADCAST: + data = createBroadcast(fromId, `https://example.com/${Math.floor(Math.random() * 100000000)}`, hash); + break; case AnnouncementTypeDto.PROFILE: + data = createProfile(fromId, `https://example.com/${Math.floor(Math.random() * 100000000)}`, hash); + break; case AnnouncementTypeDto.UPDATE: + data = createUpdate(fromId, `https://example.com/${Math.floor(Math.random() * 100000000)}`, hash, AnnouncementType.Broadcast, `${Math.floor(Math.random() * 100000000)}`); + break; case AnnouncementTypeDto.REPLY: + data = createReply( + fromId, + `https://example.com/${Math.floor(Math.random() * 100000000)}`, + hash, + `dsnp://0x${Math.floor(Math.random() * 100000000)}/0x${Math.floor(Math.random() * 100000000)}`, + ); + break; case AnnouncementTypeDto.REACTION: + data = createReaction(fromId, '🤌🏼', `dsnp://0x${Math.floor(Math.random() * 100000000)}/0x${Math.floor(Math.random() * 100000000)}`, 1); + break; case AnnouncementTypeDto.TOMBSTONE: - data = createBroadcast(`${Math.floor(Math.random() * 100000000)}`, `https://example.com/${Math.floor(Math.random() * 100000000)}`, '1289739821'); + data = createTombstone(fromId, AnnouncementType.Reply, hash); break; + default: + throw new Error('Announcement type not supported'); } // eslint-disable-next-line no-await-in-loop - const hash = await calculateDsnpHash(Buffer.from(JSON.stringify(data))); - promises.push(this.queueMapper.get(queueType)!.add(`Dummy Job - ${data.id}`, data, { jobId: hash, removeOnFail: false, removeOnComplete: 2000 })); + const jobId = await calculateDsnpHash(Buffer.from(JSON.stringify(data))); + promises.push(this.queueMapper.get(queueType)!.add(`Dummy Job - ${data.id}`, data, { jobId, removeOnFail: false, removeOnComplete: true })); } await Promise.all(promises); } diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts index 59990c12..338090ea 100644 --- a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts +++ b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts @@ -329,7 +329,7 @@ export class DsnpAnnouncementProcessor { private async processReaction(content: ReactionDto, dsnpUserId: string): Promise { this.logger.debug(`Processing reaction ${content.emoji} for ${content.inReplyTo}`); - return createReaction(dsnpUserId, content.emoji, content.inReplyTo); + return createReaction(dsnpUserId, content.emoji, content.inReplyTo, content.apply); } private async processUpdate( diff --git a/services/content-watcher/libs/common/src/interfaces/dsnp.ts b/services/content-watcher/libs/common/src/interfaces/dsnp.ts index 9f8664ec..c7ab5e3c 100644 --- a/services/content-watcher/libs/common/src/interfaces/dsnp.ts +++ b/services/content-watcher/libs/common/src/interfaces/dsnp.ts @@ -38,6 +38,7 @@ type ReactionFields = { announcementType: AnnouncementType.Reaction; emoji: string; inReplyTo: string; + apply: number; }; type ProfileFields = { @@ -48,6 +49,7 @@ type ProfileFields = { type UpdateFields = { announcementType: AnnouncementType.Update; + contentHash: string; targetAnnouncementType: AnnouncementType; targetContentHash: string; url: string; @@ -153,11 +155,13 @@ export const createReply = (fromId: string, url: string, hash: string, inReplyTo * @param fromId - The id of the user from whom the announcement is posted * @param emoji - The emoji to respond with * @param inReplyTo - The DSNP Content Uri of the parent announcement + * @param apply - * @returns A ReactionAnnouncement */ -export const createReaction = (fromId: string, emoji: string, inReplyTo: string): ReactionAnnouncement => ({ +export const createReaction = (fromId: string, emoji: string, inReplyTo: string, apply: number): ReactionAnnouncement => ({ announcementType: AnnouncementType.Reaction, emoji, + apply, fromId, inReplyTo, }); @@ -211,6 +215,7 @@ export const createNote = (content: string, published: Date, options?: Partial ({ announcementType: AnnouncementType.Update, fromId, + contentHash: hash, targetAnnouncementType: targetType, targetContentHash: targetHash, url, From 1234b30f6a94574120b877ea164f3ed479172d41 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Wed, 13 Sep 2023 09:12:18 -0700 Subject: [PATCH 022/137] refactor: move `config` and `blockchain` modules to common (#45) * refactor: move config to common * move blockchain package to common lib --- services/content-watcher/apps/api/src/api.module.ts | 4 ++-- services/content-watcher/apps/api/src/main.ts | 2 +- .../worker/src/asset_processor/asset.processor.module.ts | 4 ++-- .../worker/src/asset_processor/asset.processor.service.ts | 2 +- .../worker/src/batch_announcer/batch.announcer.module.ts | 6 +++--- .../worker/src/batch_announcer/batch.announcer.service.ts | 2 +- .../apps/worker/src/batch_announcer/batch.announcer.ts | 4 ++-- .../src/batching_processor/batching.processor.module.ts | 4 ++-- .../src/batching_processor/batching.processor.service.ts | 2 +- .../apps/worker/src/monitor/status.monitor.module.ts | 6 +++--- .../apps/worker/src/monitor/status.monitor.service.ts | 4 ++-- .../apps/worker/src/publisher/ipfs.publisher.ts | 6 +++--- .../apps/worker/src/publisher/publisher.module.ts | 6 +++--- .../apps/worker/src/publisher/publishing.service.ts | 4 ++-- .../request_processor/dsnp.announcement.processor.spec.ts | 2 +- .../src/request_processor/dsnp.announcement.processor.ts | 2 +- .../src/request_processor/request.processor.module.ts | 4 ++-- .../src/request_processor/request.processor.service.ts | 2 +- services/content-watcher/apps/worker/src/worker.module.ts | 6 +++--- .../common}/src/blockchain/blockchain-constants.ts | 0 .../common}/src/blockchain/blockchain.module.ts | 2 +- .../common}/src/blockchain/blockchain.service.spec.ts | 0 .../common}/src/blockchain/blockchain.service.ts | 2 +- .../worker => libs/common}/src/blockchain/create-keys.ts | 0 .../worker => libs/common}/src/blockchain/event-error.ts | 0 .../worker => libs/common}/src/blockchain/extrinsic.ts | 0 .../{apps/api => libs/common}/src/config/config.module.ts | 0 .../api => libs/common}/src/config/config.service.spec.ts | 0 .../{apps/api => libs/common}/src/config/config.service.ts | 2 +- .../{apps/api => libs/common}/src/config/env.config.ts | 2 +- .../{apps/api => libs/common}/src/config/swagger_config.ts | 2 +- .../common}/src/interfaces/capacity-limit.interface.ts | 0 .../content-watcher/libs/common/src/utils/ipfs.client.ts | 2 +- 33 files changed, 42 insertions(+), 42 deletions(-) rename services/content-watcher/{apps/worker => libs/common}/src/blockchain/blockchain-constants.ts (100%) rename services/content-watcher/{apps/worker => libs/common}/src/blockchain/blockchain.module.ts (80%) rename services/content-watcher/{apps/worker => libs/common}/src/blockchain/blockchain.service.spec.ts (100%) rename services/content-watcher/{apps/worker => libs/common}/src/blockchain/blockchain.service.ts (98%) rename services/content-watcher/{apps/worker => libs/common}/src/blockchain/create-keys.ts (100%) rename services/content-watcher/{apps/worker => libs/common}/src/blockchain/event-error.ts (100%) rename services/content-watcher/{apps/worker => libs/common}/src/blockchain/extrinsic.ts (100%) rename services/content-watcher/{apps/api => libs/common}/src/config/config.module.ts (100%) rename services/content-watcher/{apps/api => libs/common}/src/config/config.service.spec.ts (100%) rename services/content-watcher/{apps/api => libs/common}/src/config/config.service.ts (98%) rename services/content-watcher/{apps/api => libs/common}/src/config/env.config.ts (97%) rename services/content-watcher/{apps/api => libs/common}/src/config/swagger_config.ts (92%) rename services/content-watcher/{apps/api => libs/common}/src/interfaces/capacity-limit.interface.ts (100%) diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index a7926c67..ed7d0c95 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -4,12 +4,12 @@ import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; import { RedisModule } from '@liaoliaots/nestjs-redis'; import { ApiController } from './api.controller'; -import { ConfigService } from './config/config.service'; -import { ConfigModule } from './config/config.module'; import { DevelopmentController } from './development.controller'; import { QueueConstants } from '../../../libs/common/src'; import { ApiService } from './api.service'; import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; +import { ConfigModule } from '../../../libs/common/src/config/config.module'; +import { ConfigService } from '../../../libs/common/src/config/config.service'; @Module({ imports: [ diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index 719af90b..503fda4b 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -3,7 +3,7 @@ import { BadRequestException, Logger, ValidationPipe } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { ValidationError } from 'class-validator'; import { ApiModule } from './api.module'; -import { initSwagger } from './config/swagger_config'; +import { initSwagger } from '../../../libs/common/src/config/swagger_config'; const logger = new Logger('main'); diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts index 22bd2282..107c7bdf 100644 --- a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts +++ b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts @@ -5,11 +5,11 @@ https://docs.nestjs.com/modules import { BullModule } from '@nestjs/bullmq'; import { Module } from '@nestjs/common'; import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { ConfigModule } from '../../../api/src/config/config.module'; -import { ConfigService } from '../../../api/src/config/config.service'; import { QueueConstants } from '../../../../libs/common/src'; import { AssetProcessorService } from './asset.processor.service'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; +import { ConfigModule } from '../../../../libs/common/src/config/config.module'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; @Module({ imports: [ diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts index 4617bfcb..ebad5ea8 100644 --- a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts +++ b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts @@ -3,7 +3,7 @@ import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Injectable, Logger } from '@nestjs/common'; import { Job } from 'bullmq'; import Redis from 'ioredis'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { QueueConstants } from '../../../../libs/common/src'; import { IAssetJob } from '../../../../libs/common/src/interfaces/asset-job.interface'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts index 6c2c1f84..101c92b8 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts @@ -7,11 +7,11 @@ import { Module } from '@nestjs/common'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { RedisModule } from '@liaoliaots/nestjs-redis'; import { BatchAnnouncementService } from './batch.announcer.service'; -import { ConfigModule } from '../../../api/src/config/config.module'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigModule } from '../../../../libs/common/src/config/config.module'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { BatchAnnouncer } from './batch.announcer'; import { QueueConstants } from '../../../../libs/common/src'; -import { BlockchainModule } from '../blockchain/blockchain.module'; +import { BlockchainModule } from '../../../../libs/common/src/blockchain/blockchain.module'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; @Module({ diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts index 0bb623d2..b999f6d9 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts @@ -5,7 +5,7 @@ import { Job, Queue } from 'bullmq'; import Redis from 'ioredis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { BatchAnnouncer } from './batch.announcer'; import { CAPACITY_EPOCH_TIMEOUT_NAME } from '../../../../libs/common/src/constants'; import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts index bb6cb9b2..88039010 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts @@ -6,8 +6,8 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import Redis from 'ioredis'; import { PalletSchemasSchema } from '@polkadot/types/lookup'; import { hexToString } from '@polkadot/util'; -import { BlockchainService } from '../blockchain/blockchain.service'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; import { IPublisherJob } from '../interfaces/publisher-job.interface'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts index ed5e3a87..383b95b8 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts @@ -6,8 +6,8 @@ import { BullModule } from '@nestjs/bullmq'; import { Module } from '@nestjs/common'; import { RedisModule } from '@liaoliaots/nestjs-redis'; import { ScheduleModule } from '@nestjs/schedule'; -import { ConfigModule } from '../../../api/src/config/config.module'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigModule } from '../../../../libs/common/src/config/config.module'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { QueueConstants } from '../../../../libs/common/src'; import { BatchingProcessorService } from './batching.processor.service'; import { BroadcastWorker } from './workers/broadcast.worker'; diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts index 71b54909..e2efa6b8 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts @@ -5,7 +5,7 @@ import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { SchedulerRegistry } from '@nestjs/schedule'; import { randomUUID } from 'crypto'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { Announcement } from '../../../../libs/common/src/interfaces/dsnp'; import { RedisUtils } from '../../../../libs/common/src/utils/redis'; import { IBatchMetadata } from '../../../../libs/common/src/interfaces/batch.interface'; diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts index 70a5452f..14cff5e8 100644 --- a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts +++ b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts @@ -7,9 +7,9 @@ import { Module } from '@nestjs/common'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { RedisModule } from '@liaoliaots/nestjs-redis'; import { StatusMonitoringService } from './status.monitor.service'; -import { ConfigModule } from '../../../api/src/config/config.module'; -import { ConfigService } from '../../../api/src/config/config.service'; -import { BlockchainModule } from '../blockchain/blockchain.module'; +import { ConfigModule } from '../../../../libs/common/src/config/config.module'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; +import { BlockchainModule } from '../../../../libs/common/src/blockchain/blockchain.module'; import { QueueConstants } from '../../../../libs/common/src'; @Module({ diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts index c0347eb5..cf6a76d1 100644 --- a/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts +++ b/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts @@ -4,8 +4,8 @@ import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@ne import { Job, Queue } from 'bullmq'; import Redis from 'ioredis'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { BlockchainService } from '../blockchain/blockchain.service'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IStatusMonitorJob } from '../interfaces/status-monitor.interface'; import { QueueConstants } from '../../../../libs/common/src'; diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts index 3d374169..8859505d 100644 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -5,10 +5,10 @@ import { ISubmittableResult } from '@polkadot/types/types'; import { SubmittableExtrinsic } from '@polkadot/api-base/types'; import { InjectQueue } from '@nestjs/bullmq'; import { Hash } from '@polkadot/types/interfaces'; -import { BlockchainService } from '../blockchain/blockchain.service'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IPublisherJob } from '../interfaces/publisher-job.interface'; -import { createKeys } from '../blockchain/create-keys'; +import { createKeys } from '../../../../libs/common/src/blockchain/create-keys'; import { IStatusMonitorJob } from '../interfaces/status-monitor.interface'; import { QueueConstants } from '../../../../libs/common/src'; diff --git a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts index 1a95ca3c..0d6401ee 100644 --- a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts +++ b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts @@ -7,9 +7,9 @@ import { Module } from '@nestjs/common'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { RedisModule } from '@liaoliaots/nestjs-redis'; import { PublishingService } from './publishing.service'; -import { ConfigModule } from '../../../api/src/config/config.module'; -import { ConfigService } from '../../../api/src/config/config.service'; -import { BlockchainModule } from '../blockchain/blockchain.module'; +import { ConfigModule } from '../../../../libs/common/src/config/config.module'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; +import { BlockchainModule } from '../../../../libs/common/src/blockchain/blockchain.module'; import { IPFSPublisher } from './ipfs.publisher'; import { QueueConstants } from '../../../../libs/common/src'; diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts index b84900d3..b547c272 100644 --- a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts +++ b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts @@ -6,8 +6,8 @@ import Redis from 'ioredis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; import { MILLISECONDS_PER_SECOND } from 'time-constants'; -import { BlockchainService } from '../blockchain/blockchain.service'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IPublisherJob } from '../interfaces/publisher-job.interface'; import { IPFSPublisher } from './ipfs.publisher'; import { CAPACITY_EPOCH_TIMEOUT_NAME, SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts index 8e15b53a..5e6285ae 100644 --- a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts +++ b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts @@ -3,7 +3,7 @@ import { Queue } from 'bullmq'; import { expect, describe, it, beforeEach, jest } from '@jest/globals'; import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; import { AnnouncementTypeDto, IRequestJob, ModifiableAnnouncementTypeDto, TagTypeDto } from '../../../../libs/common/src'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; const mockQueue = { diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts index 338090ea..68fa4966 100644 --- a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts +++ b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts @@ -29,7 +29,7 @@ import { ModifiableAnnouncementTypeDto, } from '../../../../libs/common/src'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { calculateDsnpHash } from '../../../../libs/common/src/utils/ipfs'; import { AnnouncementType, diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts index 1795abaa..cabbb2eb 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts @@ -5,8 +5,8 @@ https://docs.nestjs.com/modules import { BullModule } from '@nestjs/bullmq'; import { Module } from '@nestjs/common'; import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { ConfigModule } from '../../../api/src/config/config.module'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigModule } from '../../../../libs/common/src/config/config.module'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { QueueConstants } from '../../../../libs/common/src'; import { RequestProcessorService } from './request.processor.service'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts index 1d825752..08713815 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts @@ -3,7 +3,7 @@ import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullm import { Injectable, Logger } from '@nestjs/common'; import { DelayedError, Job, Queue } from 'bullmq'; import Redis from 'ioredis'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { AnnouncementTypeDto, BroadcastDto, IRequestJob, ProfileDto, QueueConstants, ReplyDto, UpdateDto } from '../../../../libs/common/src'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts index e10fe63f..f330ad0c 100644 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -6,9 +6,7 @@ import { RedisModule } from '@liaoliaots/nestjs-redis'; import { PublishingService } from './publisher/publishing.service'; import { PublisherModule } from './publisher/publisher.module'; import { WorkerService } from './worker.service'; -import { ConfigService } from '../../api/src/config/config.service'; -import { BlockchainModule } from './blockchain/blockchain.module'; -import { ConfigModule } from '../../api/src/config/config.module'; +import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; import { BatchAnnouncementService } from './batch_announcer/batch.announcer.service'; import { BatchAnnouncerModule } from './batch_announcer/batch.announcer.module'; import { StatusMonitorModule } from './monitor/status.monitor.module'; @@ -18,6 +16,8 @@ import { AssetProcessorService } from './asset_processor/asset.processor.service import { RequestProcessorModule } from './request_processor/request.processor.module'; import { RequestProcessorService } from './request_processor/request.processor.service'; import { BatchingProcessorModule } from './batching_processor/batching.processor.module'; +import { ConfigModule } from '../../../libs/common/src/config/config.module'; +import { ConfigService } from '../../../libs/common/src/config/config.service'; @Module({ imports: [ diff --git a/services/content-watcher/apps/worker/src/blockchain/blockchain-constants.ts b/services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts similarity index 100% rename from services/content-watcher/apps/worker/src/blockchain/blockchain-constants.ts rename to services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts diff --git a/services/content-watcher/apps/worker/src/blockchain/blockchain.module.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts similarity index 80% rename from services/content-watcher/apps/worker/src/blockchain/blockchain.module.ts rename to services/content-watcher/libs/common/src/blockchain/blockchain.module.ts index 0205f784..facacf9c 100644 --- a/services/content-watcher/apps/worker/src/blockchain/blockchain.module.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts @@ -4,7 +4,7 @@ https://docs.nestjs.com/modules import { Module } from '@nestjs/common'; import { BlockchainService } from './blockchain.service'; -import { ConfigModule } from '../../../api/src/config/config.module'; +import { ConfigModule } from '../config/config.module'; @Module({ imports: [ConfigModule], diff --git a/services/content-watcher/apps/worker/src/blockchain/blockchain.service.spec.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.spec.ts similarity index 100% rename from services/content-watcher/apps/worker/src/blockchain/blockchain.service.spec.ts rename to services/content-watcher/libs/common/src/blockchain/blockchain.service.spec.ts diff --git a/services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts similarity index 98% rename from services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts rename to services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 00b303d2..d800286c 100644 --- a/services/content-watcher/apps/worker/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -9,7 +9,7 @@ import { SubmittableExtrinsic } from '@polkadot/api/types'; import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; import { u32, Option } from '@polkadot/types'; import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo, PalletSchemasSchema } from '@polkadot/types/lookup'; -import { ConfigService } from '../../../api/src/config/config.service'; +import { ConfigService } from '../config/config.service'; import { Extrinsic } from './extrinsic'; @Injectable() diff --git a/services/content-watcher/apps/worker/src/blockchain/create-keys.ts b/services/content-watcher/libs/common/src/blockchain/create-keys.ts similarity index 100% rename from services/content-watcher/apps/worker/src/blockchain/create-keys.ts rename to services/content-watcher/libs/common/src/blockchain/create-keys.ts diff --git a/services/content-watcher/apps/worker/src/blockchain/event-error.ts b/services/content-watcher/libs/common/src/blockchain/event-error.ts similarity index 100% rename from services/content-watcher/apps/worker/src/blockchain/event-error.ts rename to services/content-watcher/libs/common/src/blockchain/event-error.ts diff --git a/services/content-watcher/apps/worker/src/blockchain/extrinsic.ts b/services/content-watcher/libs/common/src/blockchain/extrinsic.ts similarity index 100% rename from services/content-watcher/apps/worker/src/blockchain/extrinsic.ts rename to services/content-watcher/libs/common/src/blockchain/extrinsic.ts diff --git a/services/content-watcher/apps/api/src/config/config.module.ts b/services/content-watcher/libs/common/src/config/config.module.ts similarity index 100% rename from services/content-watcher/apps/api/src/config/config.module.ts rename to services/content-watcher/libs/common/src/config/config.module.ts diff --git a/services/content-watcher/apps/api/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts similarity index 100% rename from services/content-watcher/apps/api/src/config/config.service.spec.ts rename to services/content-watcher/libs/common/src/config/config.service.spec.ts diff --git a/services/content-watcher/apps/api/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts similarity index 98% rename from services/content-watcher/apps/api/src/config/config.service.ts rename to services/content-watcher/libs/common/src/config/config.service.ts index 4b9ab223..515504f8 100644 --- a/services/content-watcher/apps/api/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -4,8 +4,8 @@ https://docs.nestjs.com/providers#services import { Injectable } from '@nestjs/common'; import { ConfigService as NestConfigService } from '@nestjs/config'; +import { EnvironmentDto } from '..'; import { ICapacityLimit } from '../interfaces/capacity-limit.interface'; -import { EnvironmentDto } from '../../../../libs/common/src'; export interface ConfigEnvironmentVariables { ENVIRONMENT: EnvironmentDto; diff --git a/services/content-watcher/apps/api/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts similarity index 97% rename from services/content-watcher/apps/api/src/config/env.config.ts rename to services/content-watcher/libs/common/src/config/env.config.ts index 2bc980d2..122a82ba 100644 --- a/services/content-watcher/apps/api/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -1,7 +1,7 @@ import Joi from 'joi'; import { ConfigModuleOptions } from '@nestjs/config'; import { mnemonicValidate } from '@polkadot/util-crypto'; -import { EnvironmentDto } from '../../../../libs/common/src'; +import { EnvironmentDto } from '..'; export const configModuleOptions: ConfigModuleOptions = { isGlobal: true, diff --git a/services/content-watcher/apps/api/src/config/swagger_config.ts b/services/content-watcher/libs/common/src/config/swagger_config.ts similarity index 92% rename from services/content-watcher/apps/api/src/config/swagger_config.ts rename to services/content-watcher/libs/common/src/config/swagger_config.ts index 2c6b98e1..6d2ae21e 100644 --- a/services/content-watcher/apps/api/src/config/swagger_config.ts +++ b/services/content-watcher/libs/common/src/config/swagger_config.ts @@ -1,6 +1,6 @@ import { INestApplication } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import metadata from '../metadata'; +import metadata from '../../../../apps/api/src/metadata'; export const initSwagger = async (app: INestApplication, apiPath: string) => { const options = new DocumentBuilder() diff --git a/services/content-watcher/apps/api/src/interfaces/capacity-limit.interface.ts b/services/content-watcher/libs/common/src/interfaces/capacity-limit.interface.ts similarity index 100% rename from services/content-watcher/apps/api/src/interfaces/capacity-limit.interface.ts rename to services/content-watcher/libs/common/src/interfaces/capacity-limit.interface.ts diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index 7cc9c320..082db9ad 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -9,7 +9,7 @@ import { blake2b256 as hasher } from '@multiformats/blake2/blake2b'; import { create } from 'multiformats/hashes/digest'; import { randomUUID } from 'crypto'; import { base58btc } from 'multiformats/bases/base58'; -import { ConfigService } from '../../../../apps/api/src/config/config.service'; +import { ConfigService } from '../config/config.service'; export interface FilePin { cid: string; From f7e71eed532b3efbe95b4bbd4f61bce0f0f25d62 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Wed, 20 Sep 2023 05:54:48 -0700 Subject: [PATCH 023/137] Bullui #46 (#61) * setup bull board and controller * add panel * basic bull board enabled --- .../apps/api/src/api.controller.ts | 11 +- .../apps/api/src/api.module.ts | 61 ++++ services/content-watcher/package-lock.json | 337 +++++++++++++++++- services/content-watcher/package.json | 4 + 4 files changed, 409 insertions(+), 4 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 05f9b138..6c0be0f6 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Logger, Param, ParseFilePipeBuilder, Post, Put, UploadedFiles, UseInterceptors } from '@nestjs/common'; import { FilesInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes } from '@nestjs/swagger'; +import { BullBoardInstance, InjectBullBoard } from '@bull-board/nestjs'; import { ApiService } from './api.service'; import { AnnouncementResponseDto, @@ -22,10 +23,18 @@ import { export class ApiController { private readonly logger: Logger; - constructor(private apiService: ApiService) { + constructor( + private apiService: ApiService, + @InjectBullBoard() private readonly bullBoard: BullBoardInstance, + ) { this.logger = new Logger(this.constructor.name); } + @Get('bull-board') + async getBullBoard() { + return this.bullBoard; + } + // eslint-disable-next-line class-methods-use-this @Get('health') health() { diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index ed7d0c95..9f951802 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -3,6 +3,9 @@ import { EventEmitterModule } from '@nestjs/event-emitter'; import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { BullBoardModule } from '@bull-board/nestjs'; +import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; +import { ExpressAdapter } from '@bull-board/express'; import { ApiController } from './api.controller'; import { DevelopmentController } from './development.controller'; import { QueueConstants } from '../../../libs/common/src'; @@ -18,6 +21,10 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; enableOfflineQueue: false, }, }), + BullBoardModule.forRoot({ + route: '/queues', + adapter: ExpressAdapter, + }), BullModule.registerQueue({ name: QueueConstants.REQUEST_QUEUE_NAME, }), @@ -42,6 +49,60 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; BullModule.registerQueue({ name: QueueConstants.PROFILE_QUEUE_NAME, }), + BullModule.registerQueue({ + name: QueueConstants.BATCH_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.STATUS_QUEUE_NAME, + }), + BullModule.registerQueue({ + name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, + }), + + BullBoardModule.forFeature({ + name: QueueConstants.REQUEST_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.ASSET_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.BROADCAST_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.REPLY_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.REACTION_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.TOMBSTONE_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.UPDATE_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.PROFILE_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.BATCH_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.STATUS_QUEUE_NAME, + adapter: BullMQAdapter, + }), + BullBoardModule.forFeature({ + name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, + adapter: BullMQAdapter, + }), ConfigModule, RedisModule.forRootAsync( { diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 9b176ad1..1ef72d49 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -9,6 +9,10 @@ "version": "0.1.0", "license": "Apache-2.0", "dependencies": { + "@bull-board/api": "^5.8.3", + "@bull-board/express": "^5.8.3", + "@bull-board/nestjs": "^5.8.3", + "@bull-board/ui": "^5.8.3", "@dsnp/activity-content": "^1.1.0", "@dsnp/frequency-schemas": "^1.0.2", "@dsnp/parquetjs": "^1.3.4", @@ -756,6 +760,269 @@ "dev": true, "license": "MIT" }, + "node_modules/@bull-board/api": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@bull-board/api/-/api-5.8.3.tgz", + "integrity": "sha512-xiVSXc99WQIhrsXTo/rCVxmV9uphyJA4a6ytBP7RyTGO/+IGTg7zLo2yrnYdv3p8RYw4YzdJ4aY1E+MgP5mj8Q==", + "dependencies": { + "redis-info": "^3.0.8" + }, + "peerDependencies": { + "@bull-board/ui": "5.8.3" + } + }, + "node_modules/@bull-board/express": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@bull-board/express/-/express-5.8.3.tgz", + "integrity": "sha512-zZicV35gRfnojtx+Ypg02xuk/JSSmfDaMUgGbrXfXKvq1OdQCKPkhIa9uPyHP6a7d/QLHKqzQFgJknqgQVBkxw==", + "dependencies": { + "@bull-board/api": "5.8.3", + "@bull-board/ui": "5.8.3", + "ejs": "3.1.7", + "express": "4.17.3" + } + }, + "node_modules/@bull-board/express/node_modules/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.7", + "raw-body": "2.4.3", + "type-is": "~1.6.18" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bull-board/express/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bull-board/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@bull-board/express/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bull-board/express/node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" + }, + "node_modules/@bull-board/express/node_modules/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", + "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/@bull-board/express/node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bull-board/express/node_modules/http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bull-board/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/@bull-board/express/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bull-board/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/@bull-board/express/node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@bull-board/express/node_modules/raw-body": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", + "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@bull-board/express/node_modules/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@bull-board/express/node_modules/send/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==" + }, + "node_modules/@bull-board/express/node_modules/serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/@bull-board/express/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@bull-board/nestjs": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@bull-board/nestjs/-/nestjs-5.8.3.tgz", + "integrity": "sha512-Jyr9EhHrsv1OsjZlJeInHrTR5u3UVoEYbT2WLRr1YmQmQlqIbjMjH2B7G+E6JKk5jggVEJkLi06gSN8lV9y6uw==", + "dependencies": { + "@nestjs/bull-shared": "^10.0.0" + }, + "peerDependencies": { + "@bull-board/api": "^5.8.3", + "@bull-board/express": "^5.8.3", + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^7.8.1" + } + }, + "node_modules/@bull-board/ui": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-5.8.3.tgz", + "integrity": "sha512-pQhmboukRZccs6WaTkRVGPZucL1ND8/+7JdK1pNKa0RVGVkpshH7QaHdfI30DR0h/Jv/VFaenWV1vS3KnvqeHw==", + "dependencies": { + "@bull-board/api": "5.8.3" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -3924,9 +4191,7 @@ }, "node_modules/async": { "version": "3.2.3", - "license": "MIT", - "optional": true, - "peer": true + "license": "MIT" }, "node_modules/async-limiter": { "version": "1.0.1", @@ -5299,6 +5564,20 @@ "version": "1.1.1", "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.7.tgz", + "integrity": "sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.429", "license": "ISC" @@ -6332,6 +6611,33 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/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==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.0.1", "license": "MIT", @@ -7848,6 +8154,23 @@ "node": ">=6" } }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest": { "version": "29.5.0", "dev": true, @@ -10275,6 +10598,14 @@ "node": ">=4" } }, + "node_modules/redis-info": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redis-info/-/redis-info-3.1.0.tgz", + "integrity": "sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==", + "dependencies": { + "lodash": "^4.17.11" + } + }, "node_modules/redis-parser": { "version": "3.0.0", "license": "MIT", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 2f7634d8..30e7a32f 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -37,6 +37,10 @@ }, "homepage": "https://github.com/AmplicaLabs/content-publishing-service#readme", "dependencies": { + "@bull-board/api": "^5.8.3", + "@bull-board/express": "^5.8.3", + "@bull-board/nestjs": "^5.8.3", + "@bull-board/ui": "^5.8.3", "@dsnp/activity-content": "^1.1.0", "@dsnp/frequency-schemas": "^1.0.2", "@dsnp/parquetjs": "^1.3.4", From 580f27f59e534bc0be619af6fbe27daf8e7c1785 Mon Sep 17 00:00:00 2001 From: Aramik Date: Mon, 25 Sep 2023 10:09:32 -0700 Subject: [PATCH 024/137] using configuration instead of hardcoded values (#63) * using configuration instead of hardcoded values * minor change --- .../apps/api/src/api.controller.ts | 13 +--- .../apps/api/src/api.module.ts | 19 ++++-- .../apps/api/src/api.service.ts | 2 +- services/content-watcher/apps/api/src/main.ts | 5 +- .../apps/api/test/app.e2e-spec.ts | 2 +- .../asset.processor.service.ts | 2 +- .../batching.processor.service.ts | 7 +-- .../request.processor.service.ts | 10 ++-- services/content-watcher/env.template | 28 +++++---- .../common/src/config/config.service.spec.ts | 60 +++++++++++++++++++ .../libs/common/src/config/config.service.ts | 30 ++++++++++ .../libs/common/src/config/env.config.ts | 8 ++- services/content-watcher/package.json | 12 ++-- 13 files changed, 149 insertions(+), 49 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 6c0be0f6..add2a11d 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,7 +1,6 @@ import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Logger, Param, ParseFilePipeBuilder, Post, Put, UploadedFiles, UseInterceptors } from '@nestjs/common'; import { FilesInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes } from '@nestjs/swagger'; -import { BullBoardInstance, InjectBullBoard } from '@bull-board/nestjs'; import { ApiService } from './api.service'; import { AnnouncementResponseDto, @@ -23,18 +22,10 @@ import { export class ApiController { private readonly logger: Logger; - constructor( - private apiService: ApiService, - @InjectBullBoard() private readonly bullBoard: BullBoardInstance, - ) { + constructor(private apiService: ApiService) { this.logger = new Logger(this.constructor.name); } - @Get('bull-board') - async getBullBoard() { - return this.bullBoard; - } - // eslint-disable-next-line class-methods-use-this @Get('health') health() { @@ -60,7 +51,7 @@ export class ApiController { // TODO: add a validator to check overall uploaded size .addMaxSizeValidator({ // this is in bytes (2 GB) - maxSize: 2 * 1000 * 1000 * 1000, + maxSize: parseInt(process.env.FILE_UPLOAD_MAX_SIZE_IN_BYTES!, 10), }) .build({ errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 9f951802..af9ed54a 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -26,10 +26,10 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; adapter: ExpressAdapter, }), BullModule.registerQueue({ - name: QueueConstants.REQUEST_QUEUE_NAME, + name: QueueConstants.ASSET_QUEUE_NAME, }), BullModule.registerQueue({ - name: QueueConstants.ASSET_QUEUE_NAME, + name: QueueConstants.REQUEST_QUEUE_NAME, }), BullModule.registerQueue({ name: QueueConstants.BROADCAST_QUEUE_NAME, @@ -53,18 +53,21 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; name: QueueConstants.BATCH_QUEUE_NAME, }), BullModule.registerQueue({ - name: QueueConstants.STATUS_QUEUE_NAME, + name: QueueConstants.PUBLISH_QUEUE_NAME, }), BullModule.registerQueue({ name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, }), + BullModule.registerQueue({ + name: QueueConstants.STATUS_QUEUE_NAME, + }), BullBoardModule.forFeature({ - name: QueueConstants.REQUEST_QUEUE_NAME, + name: QueueConstants.ASSET_QUEUE_NAME, adapter: BullMQAdapter, }), BullBoardModule.forFeature({ - name: QueueConstants.ASSET_QUEUE_NAME, + name: QueueConstants.REQUEST_QUEUE_NAME, adapter: BullMQAdapter, }), BullBoardModule.forFeature({ @@ -96,13 +99,17 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; adapter: BullMQAdapter, }), BullBoardModule.forFeature({ - name: QueueConstants.STATUS_QUEUE_NAME, + name: QueueConstants.PUBLISH_QUEUE_NAME, adapter: BullMQAdapter, }), BullBoardModule.forFeature({ name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, adapter: BullMQAdapter, }), + BullBoardModule.forFeature({ + name: QueueConstants.STATUS_QUEUE_NAME, + adapter: BullMQAdapter, + }), ConfigModule, RedisModule.forRootAsync( { diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index b996be87..c84c33ac 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -52,7 +52,7 @@ export class ApiService { // not used in id calculation since the order in map might not be deterministic data.assetToMimeType = assetToMimeType; } - const job = await this.requestQueue.add(`Request Job - ${data.id}`, data, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); // TODO: should come from config + const job = await this.requestQueue.add(`Request Job - ${data.id}`, data, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); // TODO: should come from queue configs this.logger.debug(job); return { referenceId: data.id, diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index 503fda4b..5cc6a304 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -1,7 +1,6 @@ import { NestFactory } from '@nestjs/core'; -import { BadRequestException, Logger, ValidationPipe } from '@nestjs/common'; +import { Logger, ValidationPipe } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { ValidationError } from 'class-validator'; import { ApiModule } from './api.module'; import { initSwagger } from '../../../libs/common/src/config/swagger_config'; @@ -27,7 +26,7 @@ async function bootstrap() { app.enableShutdownHooks(); app.useGlobalPipes(new ValidationPipe()); await initSwagger(app, '/api/docs/swagger'); - await app.listen(3000); + await app.listen(process.env.API_PORT ?? 3000); } catch (e) { await app.close(); logger.log('****** MAIN CATCH ********'); diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index 8aba5856..eac05d23 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -101,7 +101,7 @@ describe('AppController E2E request verification!', () => { .expect(202) .expect((res) => expect(res.text).toContain('referenceId'))); - it('valid request with uploaded assets should work!', async () => { + it('valid broadcast request with uploaded assets should work!', async () => { const file = Buffer.from('g'.repeat(30 * 1000 * 1000)); // 30MB const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); await sleep(1000); diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts index ebad5ea8..fcb77257 100644 --- a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts +++ b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts @@ -41,7 +41,7 @@ export class AssetProcessorService extends WorkerHost { async onCompleted(job: Job) { this.logger.log(`completed ${job.id}`); const secondsPassed = Math.round((Date.now() - job.timestamp) / 1000); - const expectedSecondsToExpire = 5 * 60; // TODO: get from config + const expectedSecondsToExpire = this.configService.getAssetExpirationIntervalSeconds(); const secondsToExpire = Math.max(0, expectedSecondsToExpire - secondsPassed); const result = await this.redis.pipeline().expire(job.data.contentLocation, secondsToExpire, 'LT').expire(job.data.metadataLocation, secondsToExpire, 'LT').exec(); this.logger.debug(result); diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts index e2efa6b8..73962619 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts @@ -32,7 +32,7 @@ export class BatchingProcessorService { const metadata = await this.getMetadataFromRedis(queueName); if (metadata) { const openTimeMs = Math.round(Date.now() - metadata.startTimestamp); - const batchTimeoutInMs = 12 * 1000; // TODO: get from config + const batchTimeoutInMs = this.configService.getBatchIntervalSeconds() * 1000; if (openTimeMs >= batchTimeoutInMs) { await this.closeBatch(queueName, metadata.batchId, false); } else { @@ -61,7 +61,7 @@ export class BatchingProcessorService { .exec(); this.logger.debug(result); - const timeout = 12 * 1000; // TODO: get from config + const timeout = this.configService.getBatchIntervalSeconds() * 1000; this.addBatchTimeout(queueName, metadata.batchId, timeout); } else { // continue on active batch @@ -75,8 +75,7 @@ export class BatchingProcessorService { .exec(); this.logger.debug(result); - // TODO: get from config - if (currentBatchMetadata.rowCount >= 100) { + if (currentBatchMetadata.rowCount >= this.configService.getBatchMaxCount()) { await this.closeBatch(queueName, currentBatchMetadata.batchId, false); } } diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts index 08713815..46b2aa6c 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts @@ -4,7 +4,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { DelayedError, Job, Queue } from 'bullmq'; import Redis from 'ioredis'; import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { AnnouncementTypeDto, BroadcastDto, IRequestJob, ProfileDto, QueueConstants, ReplyDto, UpdateDto } from '../../../../libs/common/src'; +import { IRequestJob, QueueConstants } from '../../../../libs/common/src'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; @@ -30,7 +30,7 @@ export class RequestProcessorService extends WorkerHost { const assets: string[] = job.data.assetToMimeType ? Object.keys(job.data.assetToMimeType) : []; const pinnedAssets = assets.map((cid) => this.ipfsService.getPinned(cid)); const pinnedResult = await Promise.all(pinnedAssets); - // if any of assets does not exists delay the job for a future attempt + // if any of assets does not exist delay the job for a future attempt if (pinnedResult.some((buffer) => !buffer)) { await this.delayJobAndIncrementAttempts(job); } else { @@ -51,9 +51,9 @@ export class RequestProcessorService extends WorkerHost { const { data } = job; data.dependencyAttempt += 1; if (data.dependencyAttempt <= 3) { - // attempts 10 seconds, 20 seconds, 40 seconds - const delayedTime = 2 ** data.dependencyAttempt * 5 * 1000; - await job.moveToDelayed(Date.now() + delayedTime, job.token); // TODO: get from config + // exponential backoff + const delayedTime = 2 ** data.dependencyAttempt * this.configService.getAssetUploadVerificationDelaySeconds() * 1000; + await job.moveToDelayed(Date.now() + delayedTime, job.token); await job.update(data); throw new DelayedError(); } else { diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index 4127628f..4a1c95e7 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -1,8 +1,13 @@ # Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development -IPFS_ENDPOINT="https://ipfs.infura.io:5001" -IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" -IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" -IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" +# IPFS_ENDPOINT="https://ipfs.infura.io:5001" +# IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" +# IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" +# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" +IPFS_ENDPOINT="http://127.0.0.1:5001" +IPFS_BASIC_AUTH_USER="" +IPFS_BASIC_AUTH_SECRET="" +IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" + FREQUENCY_URL=ws://0.0.0.0:9944 PROVIDER_ID=1 REDIS_URL=redis://0.0.0.0:6379 @@ -16,10 +21,13 @@ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRIES=4 CAPACITY_LIMIT='{"type":"percentage", "value":80}' -IPFS_ENDPOINT="http://127.0.0.1:5001" -IPFS_BASIC_AUTH_USER="" -IPFS_BASIC_AUTH_SECRET="" -IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" +API_PORT=3000 + +# should be dev for e2e tests. Options [dev, rococo, mainnet] +ENVIRONMENT=dev -# should be dev for e2e tests -ENVIRONMENT=dev \ No newline at end of file +FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 +ASSET_EXPIRATION_INTERVAL_SECONDS=300 +BATCH_INTERVAL_SECONDS=12 +BATCH_MAX_COUNT=1000 +ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 \ No newline at end of file diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index f75cb087..b588ae34 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -53,6 +53,12 @@ describe('ContentPublishingConfigService', () => { HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: undefined, HEALTH_CHECK_MAX_RETRIES: undefined, CAPACITY_LIMIT: undefined, + FILE_UPLOAD_MAX_SIZE_IN_BYTES: undefined, + API_PORT: undefined, + ASSET_EXPIRATION_INTERVAL_SECONDS: undefined, + BATCH_INTERVAL_SECONDS: undefined, + BATCH_MAX_COUNT: undefined, + ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: undefined, }; beforeAll(() => { @@ -163,6 +169,36 @@ describe('ContentPublishingConfigService', () => { await expect(setupConfigService({ CAPACITY_LIMIT: '{ "type": "percentage", "value": 101 }', ...env })).rejects.toBeDefined(); await expect(setupConfigService({ CAPACITY_LIMIT: '{ "type": "amount", "value": -1 }', ...env })).rejects.toBeDefined(); }); + + it('invalid max file size should fail', async () => { + const { FILE_UPLOAD_MAX_SIZE_IN_BYTES: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ FILE_UPLOAD_MAX_SIZE_IN_BYTES: -1, ...env })).rejects.toBeDefined(); + }); + + it('invalid api port should fail', async () => { + const { API_PORT: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ API_PORT: -1, ...env })).rejects.toBeDefined(); + }); + + it('invalid asset expiration interval should fail', async () => { + const { ASSET_EXPIRATION_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ ASSET_EXPIRATION_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); + }); + + it('invalid batch interval should fail', async () => { + const { BATCH_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ BATCH_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); + }); + + it('invalid batch max count should fail', async () => { + const { BATCH_MAX_COUNT: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ BATCH_MAX_COUNT: -1, ...env })).rejects.toBeDefined(); + }); + + it('invalid asset upload verification delay should fail', async () => { + const { ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: -1, ...env })).rejects.toBeDefined(); + }); }); describe('valid environment', () => { @@ -222,5 +258,29 @@ describe('ContentPublishingConfigService', () => { it('should get capacity limit', () => { expect(contentPublishingConfigService.getCapacityLimit()).toStrictEqual(JSON.parse(ALL_ENV.CAPACITY_LIMIT!)); }); + + it('should get file upload max size in bytes', () => { + expect(contentPublishingConfigService.getFileUploadMaxSizeInBytes()).toStrictEqual(parseInt(ALL_ENV.FILE_UPLOAD_MAX_SIZE_IN_BYTES as string, 10)); + }); + + it('should get api port', () => { + expect(contentPublishingConfigService.getApiPort()).toStrictEqual(parseInt(ALL_ENV.API_PORT as string, 10)); + }); + + it('should get asset expiration interval in seconds', () => { + expect(contentPublishingConfigService.getAssetExpirationIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.ASSET_EXPIRATION_INTERVAL_SECONDS as string, 10)); + }); + + it('should get asset upload verification delay in seconds', () => { + expect(contentPublishingConfigService.getAssetUploadVerificationDelaySeconds()).toStrictEqual(parseInt(ALL_ENV.ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS as string, 10)); + }); + + it('should get batch interval in seconds', () => { + expect(contentPublishingConfigService.getBatchIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.BATCH_INTERVAL_SECONDS as string, 10)); + }); + + it('should get batch max count', () => { + expect(contentPublishingConfigService.getBatchMaxCount()).toStrictEqual(parseInt(ALL_ENV.BATCH_MAX_COUNT as string, 10)); + }); }); }); diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index 515504f8..c21661f5 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -25,6 +25,12 @@ export interface ConfigEnvironmentVariables { HEALTH_CHECK_MAX_RETRIES: number; PROVIDER_ACCOUNT_SEED_PHRASE: string; CAPACITY_LIMIT: ICapacityLimit; + FILE_UPLOAD_MAX_SIZE_IN_BYTES: number; + API_PORT: number; + ASSET_EXPIRATION_INTERVAL_SECONDS: number; + BATCH_INTERVAL_SECONDS: number; + BATCH_MAX_COUNT: number; + ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: number; } /// Config service to get global app and provider-specific config values. @@ -111,4 +117,28 @@ export class ConfigService { } return gatewayUrl.replace('[CID]', cid); } + + public getFileUploadMaxSizeInBytes(): number { + return this.nestConfigService.get('FILE_UPLOAD_MAX_SIZE_IN_BYTES')!; + } + + public getApiPort(): number { + return this.nestConfigService.get('API_PORT')!; + } + + public getAssetExpirationIntervalSeconds(): number { + return this.nestConfigService.get('ASSET_EXPIRATION_INTERVAL_SECONDS')!; + } + + public getBatchIntervalSeconds(): number { + return this.nestConfigService.get('BATCH_INTERVAL_SECONDS')!; + } + + public getBatchMaxCount(): number { + return this.nestConfigService.get('BATCH_MAX_COUNT')!; + } + + public getAssetUploadVerificationDelaySeconds(): number { + return this.nestConfigService.get('ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS')!; + } } diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 122a82ba..4e868f6a 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -1,4 +1,4 @@ -import Joi from 'joi'; +import Joi, { number } from 'joi'; import { ConfigModuleOptions } from '@nestjs/config'; import { mnemonicValidate } from '@polkadot/util-crypto'; import { EnvironmentDto } from '..'; @@ -36,6 +36,12 @@ export const configModuleOptions: ConfigModuleOptions = { HEALTH_CHECK_SUCCESS_THRESHOLD: Joi.number().min(1).default(10), HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(64), HEALTH_CHECK_MAX_RETRIES: Joi.number().min(0).default(20), + FILE_UPLOAD_MAX_SIZE_IN_BYTES: Joi.number().min(1).required(), + API_PORT: Joi.number().min(0).default(3000), + ASSET_EXPIRATION_INTERVAL_SECONDS: Joi.number().min(1).required(), + BATCH_INTERVAL_SECONDS: Joi.number().min(1).required(), + BATCH_MAX_COUNT: Joi.number().min(1).required(), + ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: Joi.number().min(0).required(), CAPACITY_LIMIT: Joi.string() .custom((value: string, helpers) => { try { diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 30e7a32f..d4caabc0 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -7,23 +7,23 @@ "build": "nest build", "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", - "start": "nest start", "start:api": "nest start api", "start:worker": "nest start worker", - "start:api:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", - "start:worker:dev": "set -a ; . .env.dev ; ts-node-dev -r tsconfig-paths/register apps/worker/src/main.ts", - "start:debug": "nest start --debug --watch", + "start:api:dev": "set -a ; . .env ; nest start api", + "start:worker:dev": "set -a ; . .env ; nest start worker", + "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", + "start:worker:debug": "set -a ; . .env ;nest start worker --debug --watch", "start:dev:docker": "npm ci && ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", "docker-build": "docker build -t content-publishing-service .", "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", - "docker-run": "docker build -t content-publishing-service-deploy . ; docker run -p 6379:6379 --env-file .env.dev content-publishing-service-deploy", + "docker-run": "docker build -t content-publishing-service-deploy . ; docker run -p 6379:6379 --env-file .env content-publishing-service-deploy", "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs -f content-publishing-service", "docker-stop:dev": "docker-compose -f docker-compose.dev.yaml stop", "clean": "rm -Rf dist", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "pretest": "cp env.template .env", "test": "jest --coverage --verbose", - "test:e2e": "jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles" + "test:e2e": "set -a ; . .env ; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles" }, "repository": { "type": "git", From 24d4b9362dd4b31fe9a34c09ee54a4fab5ec13d0 Mon Sep 17 00:00:00 2001 From: Aramik Date: Mon, 25 Sep 2023 13:03:05 -0700 Subject: [PATCH 025/137] bugfix: allow checking content pinned status without timeout (#66) --- .../apps/api/src/development.controller.ts | 12 ++---- .../apps/api/test/app.e2e-spec.ts | 5 +++ .../request.processor.service.ts | 2 +- .../libs/common/src/utils/ipfs.client.ts | 37 ++++++++++++++++++- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/services/content-watcher/apps/api/src/development.controller.ts b/services/content-watcher/apps/api/src/development.controller.ts index a29bc104..79c5d048 100644 --- a/services/content-watcher/apps/api/src/development.controller.ts +++ b/services/content-watcher/apps/api/src/development.controller.ts @@ -3,7 +3,7 @@ This is a controller providing some endpoints useful for development and testing */ // eslint-disable-next-line max-classes-per-file -import { Controller, Get, Logger, Param, Post } from '@nestjs/common'; +import { Controller, Get, Logger, NotFoundException, Param, Post } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { Job } from 'bullmq/dist/esm/classes/job'; @@ -50,14 +50,10 @@ export class DevelopmentController { @Get('/asset/:assetId') // eslint-disable-next-line consistent-return async getAsset(@Param('assetId') assetId: string) { - try { - return this.ipfsService.getPinned(assetId); - } catch (error: any) { - if (error.response) { - console.error(error.response.data); - } - throw error; + if (await this.ipfsService.isPinned(assetId)) { + return this.ipfsService.getPinned(assetId, false); } + throw new NotFoundException(`${assetId} does not exist`); } @Post('/dummy/announcement/:queueType/:count') diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index eac05d23..19b1768a 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -815,6 +815,11 @@ describe('AppController E2E request verification!', () => { .expect(200) .expect((res) => expect(Buffer.from(res.body)).toEqual(Buffer.from(buffer))); }, 15000); + + it('not uploaded asset should return not found', async () => { + const assetId = 'bafybeieva67sj7hiiywi4kxsamcc2t2y2pptni2ki6gu63azj3pkznbzna'; + return request(app.getHttpServer()).get(`/api/dev/asset/${assetId}`).expect(404); + }); }); afterEach(async () => { diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts index 46b2aa6c..f02cf836 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts @@ -31,7 +31,7 @@ export class RequestProcessorService extends WorkerHost { const pinnedAssets = assets.map((cid) => this.ipfsService.getPinned(cid)); const pinnedResult = await Promise.all(pinnedAssets); // if any of assets does not exist delay the job for a future attempt - if (pinnedResult.some((buffer) => !buffer)) { + if (pinnedResult.some((buffer) => !buffer || buffer.length === 0)) { await this.delayJobAndIncrementAttempts(job); } else { await this.dsnpAnnouncementProcessor.collectAnnouncementAndQueue(job.data); diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index 082db9ad..39c0d1a6 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -75,8 +75,18 @@ export class IpfsService { return { ...ipfs, hash: calculateDsnpHash ? fileName : '' }; } - // TODO: bugfix: when the cid does not exist the endpoint will get stuck indefinitely - public async getPinned(cid: string): Promise { + /** + * returns pinned file + * if the file does not exist the request will time out, so it is recommended to always check existence before calling + * the cat endpoint + * @param cid + * @param checkExistence + * @returns buffer of the data if exists and an empty buffer if not + */ + public async getPinned(cid: string, checkExistence: boolean = true): Promise { + if (checkExistence && !(await this.isPinned(cid))) { + return Promise.resolve(Buffer.alloc(0)); + } const ipfsGet = `${this.configService.getIpfsEndpoint()}/api/v0/cat?arg=${cid}`; const ipfsAuthUser = this.configService.getIpfsBasicAuthUser(); const ipfsAuthSecret = this.configService.getIpfsBasicAuthSecret(); @@ -94,6 +104,29 @@ export class IpfsService { return data; } + public async isPinned(cid: string): Promise { + const parsedCid = CID.parse(cid); + const v0Cid = parsedCid.toV0().toString(); + const ipfsGet = `${this.configService.getIpfsEndpoint()}/api/v0/pin/ls?type=all&quiet=true&arg=${v0Cid}`; + const ipfsAuthUser = this.configService.getIpfsBasicAuthUser(); + const ipfsAuthSecret = this.configService.getIpfsBasicAuthSecret(); + const ipfsAuth = ipfsAuthUser && ipfsAuthSecret ? `Basic ${Buffer.from(`${ipfsAuthUser}:${ipfsAuthSecret}`).toString('base64')}` : ''; + + const headers = { + Accept: '*/*', + Connection: 'keep-alive', + authorization: ipfsAuth, + }; + + const response = await axios.post(ipfsGet, null, { headers, responseType: 'json' }).catch((error) => { + // when pid does not exist this call returns 500 which is not great + if (error.response && error.response.status !== 500) { + this.logger.error(error.toJSON()); + } + }); + return response && response.data && JSON.stringify(response.data).indexOf(v0Cid) >= 0; + } + private async ipfsHashBuffer(fileBuffer: Buffer): Promise { this.logger.debug(`Hashing file buffer with length: ${fileBuffer.length}`); const hashed = await hasher.digest(fileBuffer); From 0a0a8bd6e921f01dc751977233ec89a831e874bb Mon Sep 17 00:00:00 2001 From: Aramik Date: Thu, 28 Sep 2023 11:01:02 -0700 Subject: [PATCH 026/137] using atmoic transactions (#69) --- .../apps/api/src/api.service.ts | 62 ++++++---- .../content-watcher/apps/api/src/metadata.ts | 2 +- .../src/batch_announcer/batch.announcer.ts | 3 +- .../batching.processor.service.ts | 117 +++++++++++------- .../workers/broadcast.worker.ts | 9 +- .../workers/profile.worker.ts | 9 +- .../workers/reaction.worker.ts | 9 +- .../workers/reply.worker.ts | 9 +- .../workers/tombstone.worker.ts | 9 +- .../workers/update.worker.ts | 9 +- .../libs/common/src/utils/redis.ts | 21 +++- services/content-watcher/lua/addToBatch.lua | 37 ++++++ services/content-watcher/lua/lockBatch.lua | 53 ++++++++ 13 files changed, 235 insertions(+), 114 deletions(-) create mode 100644 services/content-watcher/lua/addToBatch.lua create mode 100644 services/content-watcher/lua/lockBatch.lua diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index c84c33ac..1c3b78e6 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -97,21 +97,28 @@ export class ApiService { return map; } - // TODO: make all these operations transactional - // eslint-disable-next-line no-undef,class-methods-use-this + // eslint-disable-next-line no-undef async addAssets(files: Array): Promise { // calculate ipfs cid references const referencePromises: Promise[] = files.map((file) => calculateIpfsCID(file.buffer)); const references = await Promise.all(referencePromises); - // add assets to redis - const redisDataOps = files.map((f, index) => this.redis.set(getAssetDataKey(references[index]), f.buffer)); - const addedData = await Promise.all(redisDataOps); - this.logger.debug(addedData); - - // add asset jobs to the queue + let dataTransaction = this.redis.multi(); + let metadataTransaction = this.redis.multi(); const jobs: any[] = []; files.forEach((f, index) => { + // adding data and metadata to the transaction + dataTransaction = dataTransaction.setex(getAssetDataKey(references[index]), RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, f.buffer); + metadataTransaction = metadataTransaction.setex( + getAssetMetadataKey(references[index]), + RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, + JSON.stringify({ + ipfsCid: references[index], + mimeType: f.mimetype, + createdOn: Date.now(), + } as IAssetMetadata), + ); + // adding asset job to the jobs jobs.push({ name: `Asset Job - ${references[index]}`, data: { @@ -132,22 +139,23 @@ export class ApiService { } as BulkJobOptions, }); }); + + // currently we are applying 3 different transactions on redis + // 1: Storing the content data + // 2: Adding asset jobs + // 3: Storing the content metadata + // even though all these transactions are applied separately, the overall behavior will clean up any partial failures eventually + // partial failure scenarios: + // 1: adding jobs failure: at this point we already successfully stored the data content in redis, but since all + // of this stored data has expire-time, it would eventually get cleaned up + // 2: metadata transaction failure: at this point we already stored the data content and jobs and those two are + // enough to process the asset on the worker side, the worker will clean up both of them after processing + const dataOps = await dataTransaction.exec(); + this.checkTransactionResult(dataOps); const queuedJobs = await this.assetQueue.addBulk(jobs); this.logger.debug(queuedJobs); - - // add metadata to redis - const redisMetadataOps = files.map((f, index) => - this.redis.set( - getAssetMetadataKey(references[index]), - JSON.stringify({ - ipfsCid: references[index], - mimeType: f.mimetype, - createdOn: Date.now(), - } as IAssetMetadata), - ), - ); - const addedMetadata = await Promise.all(redisMetadataOps); - this.logger.debug(addedMetadata); + const metaDataOps = await metadataTransaction.exec(); + this.checkTransactionResult(metaDataOps); return { assetIds: references, @@ -159,4 +167,14 @@ export class ApiService { const stringVal = JSON.stringify(jobWithoutId); return createHash('sha1').update(stringVal).digest('base64url'); } + + private checkTransactionResult(result: [error: Error | null, result: unknown][] | null) { + this.logger.log(result); + for (let index = 0; result && index < result.length; index += 1) { + const [err, _id] = result[index]; + if (err) { + throw err; + } + } + } } diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index 530eafc4..d42ac984 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -5,5 +5,5 @@ export default async () => { ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), ["../../../libs/common/src/dtos/common.dto"]: await import("../../../libs/common/src/dtos/common.dto") }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }], [import("./development.controller"), { "DevelopmentController": { "requestJob": {}, "getAsset": { type: Object } } }]] } }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }], [import("./development.controller"), { "DevelopmentController": { "requestJob": {}, "getAsset": { type: Object }, "populate": {} } }]] } }; }; \ No newline at end of file diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts index 88039010..ebac079c 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts @@ -6,6 +6,7 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import Redis from 'ioredis'; import { PalletSchemasSchema } from '@polkadot/types/lookup'; import { hexToString } from '@polkadot/util'; +import { RedisUtils } from '../../../../libs/common/src/utils/redis'; import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; @@ -34,7 +35,7 @@ export class BatchAnnouncer { if (!cachedSchema) { const schemaResponse = await this.blockchainService.getSchema(schemaId); cachedSchema = JSON.stringify(schemaResponse); - await this.cacheManager.set(schemaCacheKey, cachedSchema); + await this.cacheManager.setex(schemaCacheKey, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, cachedSchema); } const frequencySchema: PalletSchemasSchema = JSON.parse(cachedSchema); diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts index 73962619..9518de57 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts @@ -5,6 +5,7 @@ import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { SchedulerRegistry } from '@nestjs/schedule'; import { randomUUID } from 'crypto'; +import * as fs from 'fs'; import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { Announcement } from '../../../../libs/common/src/interfaces/dsnp'; import { RedisUtils } from '../../../../libs/common/src/utils/redis'; @@ -14,6 +15,7 @@ import getBatchDataKey = RedisUtils.getBatchDataKey; import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; import { DsnpSchemas } from '../../../../libs/common/src/utils/dsnp.schema'; import { QueueConstants } from '../../../../libs/common/src'; +import getBatchLockKey = RedisUtils.getLockKey; @Injectable() export class BatchingProcessorService { @@ -26,6 +28,14 @@ export class BatchingProcessorService { private configService: ConfigService, ) { this.logger = new Logger(this.constructor.name); + redis.defineCommand('addToBatch', { + numberOfKeys: 2, + lua: fs.readFileSync('lua/addToBatch.lua', 'utf8'), + }); + redis.defineCommand('lockBatch', { + numberOfKeys: 4, + lua: fs.readFileSync('lua/lockBatch.lua', 'utf8'), + }); } async setupActiveBatchTimeout(queueName: string) { @@ -45,39 +55,25 @@ export class BatchingProcessorService { async process(job: Job, queueName: string): Promise { this.logger.log(`Processing job ${job.id} from ${queueName}`); - const currentBatchMetadata = await this.getMetadataFromRedis(queueName); - if (!currentBatchMetadata) { - this.logger.log(`Processing job ${job.id} no current batch`); - // No active batch exists, creating a new one - const metadata = { - batchId: randomUUID().toString(), - startTimestamp: Date.now(), - rowCount: 1, - } as IBatchMetadata; - const result = await this.redis - .multi() - .set(getBatchMetadataKey(queueName), JSON.stringify(metadata)) - .hsetnx(getBatchDataKey(queueName), job.id!, JSON.stringify(job.data)) - .exec(); - this.logger.debug(result); + const batchId = randomUUID().toString(); + const newMetadata = JSON.stringify({ + batchId, + startTimestamp: Date.now(), + rowCount: 1, + } as IBatchMetadata); + const newData = JSON.stringify(job.data); + // @ts-ignore + const rowCount = await this.redis.addToBatch(getBatchMetadataKey(queueName), getBatchDataKey(queueName), newMetadata, job.id!, newData); + this.logger.log(rowCount); + if (rowCount === 1) { + this.logger.log(`Processing job ${job.id} with a new batch`); const timeout = this.configService.getBatchIntervalSeconds() * 1000; - this.addBatchTimeout(queueName, metadata.batchId, timeout); - } else { - // continue on active batch - this.logger.log(`Processing job ${job.id} existing batch ${currentBatchMetadata.batchId}`); - - currentBatchMetadata.rowCount += 1; - const result = await this.redis - .multi() - .set(getBatchMetadataKey(queueName), JSON.stringify(currentBatchMetadata)) - .hsetnx(getBatchDataKey(queueName), job.id!, JSON.stringify(job.data)) - .exec(); - this.logger.debug(result); - - if (currentBatchMetadata.rowCount >= this.configService.getBatchMaxCount()) { - await this.closeBatch(queueName, currentBatchMetadata.batchId, false); - } + this.addBatchTimeout(queueName, batchId, timeout); + } else if (rowCount >= this.configService.getBatchMaxCount()) { + await this.closeBatch(queueName, batchId, false); + } else if (rowCount === -1) { + throw new Error(`invalid result from addingToBatch for job ${job.id} and queue ${queueName} ${this.configService.getBatchMaxCount()}`); } } @@ -87,22 +83,51 @@ export class BatchingProcessorService { private async closeBatch(queueName: string, batchId: string, timeout: boolean) { this.logger.log(`Closing batch for ${queueName} ${batchId} ${timeout}`); - const metadata = await this.getMetadataFromRedis(queueName); - const batch = await this.redis.hgetall(getBatchDataKey(queueName)); + + const batchMetaDataKey = getBatchMetadataKey(queueName); + const batchDataKey = getBatchDataKey(queueName); + const lockedBatchMetaDataKey = getBatchLockKey(batchMetaDataKey); + const lockedBatchDataKey = getBatchLockKey(batchDataKey); + // @ts-ignore + const response = await this.redis.lockBatch( + batchMetaDataKey, + batchDataKey, + lockedBatchMetaDataKey, + lockedBatchDataKey, + Date.now(), + RedisUtils.BATCH_LOCK_EXPIRE_SECONDS * 1000, + ); + this.logger.debug(JSON.stringify(response)); + const status = response[0]; + + if (status === 0) { + this.logger.log(`No meta-data for closing batch ${queueName} ${batchId}. Ignore...`); + return; + } + if (status === -2) { + this.logger.log(`Previous batch is still locked ${queueName}. Ignore...`); + return; + } + if (status === -1) { + this.logger.log(`Previous batch is not closed for ${queueName} and we are going to close it first`); + } + const batch = response[1]; + const metaData: IBatchMetadata = JSON.parse(response[2]); const announcements: Announcement[] = []; - Object.keys(batch).forEach((key) => { - const announcement: Announcement = JSON.parse(batch[key]); + for (let i = 0; i < batch.length; i += 2) { + const announcement: Announcement = JSON.parse(batch[i + 1]); announcements.push(announcement); - }); - const job = { - batchId, - schemaId: DsnpSchemas.getSchemaId(this.configService.environment, QueueConstants.QUEUE_NAME_TO_ANNOUNCEMENT_MAP.get(queueName)!), - announcements, - } as IBatchAnnouncerJobData; - await this.outputQueue.add(`Batch Job - ${metadata?.batchId}`, job, { jobId: metadata?.batchId, removeOnFail: false, removeOnComplete: 100 }); - this.logger.debug(batch); + } + if (announcements.length > 0) { + const job = { + batchId: metaData.batchId, + schemaId: DsnpSchemas.getSchemaId(this.configService.environment, QueueConstants.QUEUE_NAME_TO_ANNOUNCEMENT_MAP.get(queueName)!), + announcements, + } as IBatchAnnouncerJobData; + await this.outputQueue.add(`Batch Job - ${metaData.batchId}`, job, { jobId: metaData.batchId, removeOnFail: false, removeOnComplete: 100 }); + } try { - const result = await this.redis.multi().del(getBatchMetadataKey(queueName)).del(getBatchDataKey(queueName)).exec(); + const result = await this.redis.multi().del(lockedBatchMetaDataKey).del(lockedBatchDataKey).exec(); this.logger.debug(result); const timeoutName = BatchingProcessorService.getTimeoutName(queueName, batchId); if (this.schedulerRegistry.doesExist('timeout', timeoutName)) { @@ -111,6 +136,10 @@ export class BatchingProcessorService { } catch (e) { this.logger.error(e); } + if (status === -1) { + this.logger.log(`after closing the previous leftover locked batch now we are going to close ${queueName}`); + await this.closeBatch(queueName, batchId, timeout); + } } private async getMetadataFromRedis(queueName: string): Promise { diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts index 42c7c056..a948e5c5 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts @@ -1,21 +1,16 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; -import Redis from 'ioredis'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; @Injectable() -@Processor(QueueConstants.BROADCAST_QUEUE_NAME) +@Processor(QueueConstants.BROADCAST_QUEUE_NAME, { concurrency: 2 }) export class BroadcastWorker extends WorkerHost implements OnApplicationBootstrap { private logger: Logger; - constructor( - @InjectRedis() private redis: Redis, - private batchingProcessorService: BatchingProcessorService, - ) { + constructor(private batchingProcessorService: BatchingProcessorService) { super(); this.logger = new Logger(this.constructor.name); } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts index f1993b20..ab229830 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts @@ -1,21 +1,16 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; -import Redis from 'ioredis'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; @Injectable() -@Processor(QueueConstants.PROFILE_QUEUE_NAME) +@Processor(QueueConstants.PROFILE_QUEUE_NAME, { concurrency: 2 }) export class ProfileWorker extends WorkerHost implements OnApplicationBootstrap { private logger: Logger; - constructor( - @InjectRedis() private redis: Redis, - private batchingProcessorService: BatchingProcessorService, - ) { + constructor(private batchingProcessorService: BatchingProcessorService) { super(); this.logger = new Logger(this.constructor.name); } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts index ce47207d..9b225a22 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts @@ -1,21 +1,16 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; -import Redis from 'ioredis'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; @Injectable() -@Processor(QueueConstants.REACTION_QUEUE_NAME) +@Processor(QueueConstants.REACTION_QUEUE_NAME, { concurrency: 2 }) export class ReactionWorker extends WorkerHost implements OnApplicationBootstrap { private logger: Logger; - constructor( - @InjectRedis() private redis: Redis, - private batchingProcessorService: BatchingProcessorService, - ) { + constructor(private batchingProcessorService: BatchingProcessorService) { super(); this.logger = new Logger(this.constructor.name); } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts index 8d292e9d..e7d4469f 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts @@ -1,21 +1,16 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; -import Redis from 'ioredis'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; @Injectable() -@Processor(QueueConstants.REPLY_QUEUE_NAME) +@Processor(QueueConstants.REPLY_QUEUE_NAME, { concurrency: 2 }) export class ReplyWorker extends WorkerHost implements OnApplicationBootstrap { private logger: Logger; - constructor( - @InjectRedis() private redis: Redis, - private batchingProcessorService: BatchingProcessorService, - ) { + constructor(private batchingProcessorService: BatchingProcessorService) { super(); this.logger = new Logger(this.constructor.name); } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts index a848bd92..6a287084 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts @@ -1,21 +1,16 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; -import Redis from 'ioredis'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; @Injectable() -@Processor(QueueConstants.TOMBSTONE_QUEUE_NAME) +@Processor(QueueConstants.TOMBSTONE_QUEUE_NAME, { concurrency: 2 }) export class TombstoneWorker extends WorkerHost implements OnApplicationBootstrap { private logger: Logger; - constructor( - @InjectRedis() private redis: Redis, - private batchingProcessorService: BatchingProcessorService, - ) { + constructor(private batchingProcessorService: BatchingProcessorService) { super(); this.logger = new Logger(this.constructor.name); } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts index 2d7838d9..ca8a70ae 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts @@ -1,21 +1,16 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; -import Redis from 'ioredis'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; @Injectable() -@Processor(QueueConstants.UPDATE_QUEUE_NAME) +@Processor(QueueConstants.UPDATE_QUEUE_NAME, { concurrency: 2 }) export class UpdateWorker extends WorkerHost implements OnApplicationBootstrap { private logger: Logger; - constructor( - @InjectRedis() private redis: Redis, - private batchingProcessorService: BatchingProcessorService, - ) { + constructor(private batchingProcessorService: BatchingProcessorService) { super(); this.logger = new Logger(this.constructor.name); } diff --git a/services/content-watcher/libs/common/src/utils/redis.ts b/services/content-watcher/libs/common/src/utils/redis.ts index 73372d62..6e02db33 100644 --- a/services/content-watcher/libs/common/src/utils/redis.ts +++ b/services/content-watcher/libs/common/src/utils/redis.ts @@ -1,8 +1,17 @@ export namespace RedisUtils { - const ASSET_DATA_KEY_PREFIX = 'asset::data'; - const ASSET_METADATA_KEY_PREFIX = 'asset::metadata'; - const BATCH_DATA_KEY_PREFIX = 'batch::data'; - const BATCH_METADATA_KEY_PREFIX = 'batch::metadata'; + /** + * 45 days upper limit to avoid keeping abandoned data forever + */ + export const STORAGE_EXPIRE_UPPER_LIMIT_SECONDS = 45 * 24 * 60 * 60; + /** + * batch Lock expire time which applies during closing operation + */ + export const BATCH_LOCK_EXPIRE_SECONDS = 6; + const ASSET_DATA_KEY_PREFIX = 'asset:data'; + const ASSET_METADATA_KEY_PREFIX = 'asset:metadata'; + const BATCH_DATA_KEY_PREFIX = 'batch:data'; + const BATCH_METADATA_KEY_PREFIX = 'batch:metadata'; + const LOCK_KEY_PREFIX = 'locked'; export function getAssetDataKey(assetId: string) { return `${ASSET_DATA_KEY_PREFIX}:${assetId}`; @@ -19,4 +28,8 @@ export namespace RedisUtils { export function getBatchMetadataKey(queueName: string) { return `${BATCH_METADATA_KEY_PREFIX}:${queueName}`; } + + export function getLockKey(suffix: string) { + return `${LOCK_KEY_PREFIX}:${suffix}`; + } } diff --git a/services/content-watcher/lua/addToBatch.lua b/services/content-watcher/lua/addToBatch.lua new file mode 100644 index 00000000..60c220a0 --- /dev/null +++ b/services/content-watcher/lua/addToBatch.lua @@ -0,0 +1,37 @@ +--[[ +Input: +KEYS[1] batch metadata key +KEYS[2] batch data key +ARGV[1] metadata +ARGV[2] Job id +ARGV[3] data +Output: +-1 ERROR +1 OK (new batch) +N OK (number of existing rows) +]] +local batchMetadataKey = KEYS[1] +local batchDataKey = KEYS[2] +local newMetadata = ARGV[1] +local newJobId = ARGV[2] +local newData = ARGV[3] +local rcall = redis.call +local rawMetadata = rcall("GET",batchMetadataKey) +local currentCount = 1 +if rawMetadata then + local metadata = cjson.decode(rawMetadata) + local rowCount = metadata['rowCount'] + if not rowCount then + return -1 + end + currentCount = rowCount + 1 + metadata['rowCount'] = currentCount + rcall("SET", batchMetadataKey, cjson.encode(metadata)) +else + rcall("SET", batchMetadataKey, newMetadata) +end +rcall("HSETNX", batchDataKey, newJobId, newData) +return currentCount + + + diff --git a/services/content-watcher/lua/lockBatch.lua b/services/content-watcher/lua/lockBatch.lua new file mode 100644 index 00000000..7b9b62bf --- /dev/null +++ b/services/content-watcher/lua/lockBatch.lua @@ -0,0 +1,53 @@ +--[[ +Input: +KEYS[1] batch metadata key +KEYS[2] batch data key +KEYS[3] locked metadata key +KEYS[4] locked data key +ARGV[1] current timestamp +ARGV[2] timestamp interval +Output: +-1 ERROR (lock existed for more than timeout) +-2 ERROR (lock existed for less than timeout) +0 OK (there is no batch to close) +1 OK (locking the batch) +if the previous batch is still locked then return the data for that batch +if no locked previous batch then just return the data for current batch +]] +local batchMetadataKey = KEYS[1] +local batchDataKey = KEYS[2] +local lockedMetadataKey = KEYS[3] +local lockedDataKey = KEYS[4] +local currentTimestamp = tonumber(ARGV[1]) +local timestampInterval = tonumber(ARGV[2]) +local rcall = redis.call + +local metadata = rcall("GET",batchMetadataKey) +if not metadata then + return {0} +end + +local rawPreviousMetadata = rcall('GET', lockedMetadataKey) +if rawPreviousMetadata then + local previousMetadata = cjson.decode(rawPreviousMetadata) + local timePassed = currentTimestamp - tonumber(previousMetadata['lockTimestamp']) + if timePassed > timestampInterval then + return {-1, rcall('HGETALL', lockedDataKey), rawPreviousMetadata} + else + return {-2} + end +end + +rcall("RENAME",batchMetadataKey, lockedMetadataKey) +rcall("RENAME",batchDataKey, lockedDataKey) + +local rawPreviousMetadata2 = rcall('GET', lockedMetadataKey) +local previousMetadata2 = cjson.decode(rawPreviousMetadata2) +previousMetadata2['lockTimestamp']=currentTimestamp +local encodedMetadata = cjson.encode(previousMetadata2) +rcall('SET', lockedMetadataKey, encodedMetadata) + +return {1, rcall('HGETALL', lockedDataKey), encodedMetadata} + + + From a93eed9635c0cddf7b18804b7a7beaa8c79e3b6f Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:49:41 -0500 Subject: [PATCH 027/137] [Status Monitor] Check for the transaction in receipt queue (#62) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Tx // 🐂 // * lint * updates * check if tx is successful * handle some tx checks * incremental * delay publish job it not found * send to txReceipt queue * fixes * add and forget * tdd * cleanup and test * send tx with a delay of a block * set used capacity from tx per epoch * finalize * Update apps/worker/src/monitor/tx.status.monitor.service.ts Co-authored-by: Joe Caputo * re-queue publish jpb if set attempts to find tx failed * throw error to retry the job * handle failure to find tx * same jobid * add TODO --------- Co-authored-by: Joe Caputo --- .../interfaces/status-monitor.interface.ts | 9 +- .../src/monitor/status.monitor.module.ts | 8 +- .../src/monitor/status.monitor.service.ts | 60 -------- .../src/monitor/tx.status.monitor.service.ts | 140 ++++++++++++++++++ .../worker/src/publisher/ipfs.publisher.ts | 40 ++--- .../worker/src/publisher/publisher.module.ts | 2 +- .../src/publisher/publishing.service.ts | 42 +++--- .../apps/worker/src/worker.module.ts | 4 +- .../src/blockchain/blockchain-constants.ts | 14 +- .../src/blockchain/blockchain.service.ts | 15 +- .../libs/common/src/blockchain/extrinsic.ts | 24 ++- 11 files changed, 222 insertions(+), 136 deletions(-) delete mode 100644 services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts create mode 100644 services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts diff --git a/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts b/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts index c896f558..69f88f9d 100644 --- a/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts +++ b/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts @@ -1,7 +1,10 @@ -import { Hash } from '@polkadot/types/interfaces'; +import { BlockHash, Hash } from '@polkadot/types/interfaces'; +import { IPublisherJob } from './publisher-job.interface'; -export interface IStatusMonitorJob { +export interface ITxMonitorJob { id: string; txHash: Hash; - publisherJobId: string; + epoch: string; + lastFinalizedBlockHash: BlockHash; + referencePublishJob: IPublisherJob; } diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts index 14cff5e8..c8690822 100644 --- a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts +++ b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts @@ -6,7 +6,7 @@ import { BullModule } from '@nestjs/bullmq'; import { Module } from '@nestjs/common'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { StatusMonitoringService } from './status.monitor.service'; +import { TxStatusMonitoringService } from './tx.status.monitor.service'; import { ConfigModule } from '../../../../libs/common/src/config/config.module'; import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { BlockchainModule } from '../../../../libs/common/src/blockchain/blockchain.module'; @@ -54,7 +54,7 @@ import { QueueConstants } from '../../../../libs/common/src'; { name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, defaultJobOptions: { - attempts: 1, + attempts: 3, backoff: { type: 'exponential', }, @@ -76,7 +76,7 @@ import { QueueConstants } from '../../../../libs/common/src'; ), ], controllers: [], - providers: [StatusMonitoringService], - exports: [BullModule, StatusMonitoringService], + providers: [TxStatusMonitoringService], + exports: [BullModule, TxStatusMonitoringService], }) export class StatusMonitorModule {} diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts deleted file mode 100644 index cf6a76d1..00000000 --- a/services/content-watcher/apps/worker/src/monitor/status.monitor.service.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; -import { Job, Queue } from 'bullmq'; -import Redis from 'ioredis'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { IStatusMonitorJob } from '../interfaces/status-monitor.interface'; -import { QueueConstants } from '../../../../libs/common/src'; - -@Injectable() -@Processor(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, { - concurrency: 2, -}) -export class StatusMonitoringService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { - private logger: Logger; - - constructor( - @InjectRedis() private cacheManager: Redis, - @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, - private blockchainService: BlockchainService, - private configService: ConfigService, - private eventEmitter: EventEmitter2, - ) { - super(); - this.logger = new Logger(this.constructor.name); - } - - public async onApplicationBootstrap() { - this.logger.debug('Starting publishing service'); - } - - public onModuleDestroy() { - try { - this.logger.debug('Shutting down publishing service'); - } catch (e) { - // 💀 // - } - } - - async process(job: Job): Promise { - this.logger.log(`Processing job ${job.id} of type ${job.name}`); - try { - this.logger.verbose(`Successfully completed job ${job.id}`); - return { success: true }; - } catch (e) { - this.logger.error(`Job ${job.id} failed (attempts=${job.attemptsMade})`); - throw e; - } finally { - // do some stuff - } - } - - // eslint-disable-next-line class-methods-use-this - @OnWorkerEvent('completed') - onCompleted() { - // do some stuff - } -} diff --git a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts new file mode 100644 index 00000000..e5383bd0 --- /dev/null +++ b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts @@ -0,0 +1,140 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; +import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; +import Redis from 'ioredis'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { MILLISECONDS_PER_SECOND } from 'time-constants'; +import { BlockHash, Hash } from '@polkadot/types/interfaces'; +import { map, tap, timeout } from 'rxjs'; +import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; +import { ITxMonitorJob } from '../interfaces/status-monitor.interface'; +import { QueueConstants } from '../../../../libs/common/src'; +import { SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; +import { BlockchainConstants } from '../../../../libs/common/src/blockchain/blockchain-constants'; + +@Injectable() +@Processor(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, { + concurrency: 2, +}) +export class TxStatusMonitoringService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { + private logger: Logger; + + constructor( + @InjectRedis() private cacheManager: Redis, + @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue, + @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, + private blockchainService: BlockchainService, + private configService: ConfigService, + private eventEmitter: EventEmitter2, + ) { + super(); + this.logger = new Logger(this.constructor.name); + } + + public async onApplicationBootstrap() { + this.logger.debug('Starting publishing service'); + } + + public onModuleDestroy() { + try { + this.logger.debug('Shutting down publishing service'); + } catch (e) { + // 🐂 // + } + } + + async process(job: Job): Promise { + this.logger.log(`Monitoring job ${job.id} of type ${job.name}`); + try { + const numberBlocksToParse = BlockchainConstants.NUMBER_BLOCKS_TO_CRAWL; + const txCapacityEpoch = job.data.epoch; + const previousKnownBlockNumber = (await this.blockchainService.getBlock(job.data.lastFinalizedBlockHash)).block.header.number.toBigInt(); + const currentFinalizedBlockNumber = await this.blockchainService.getLatestFinalizedBlockNumber(); + const blockList: bigint[] = []; + for (let i = previousKnownBlockNumber; i <= currentFinalizedBlockNumber && i < previousKnownBlockNumber + numberBlocksToParse; i += 1n) { + blockList.push(i); + } + const txBlockHash = await this.crawlBlockList(job.data.txHash, txCapacityEpoch, blockList); + + if (txBlockHash) { + this.logger.verbose(`Successfully completed job ${job.id}`); + return { success: true }; + } + + // handle failure to find tx in block list after + // TODO - handle requeing of publish job in case of failure + // Issue: https://github.com/AmplicaLabs/content-publishing-service/issues/18 + if (!txBlockHash && job.attemptsMade >= (job.opts.attempts ?? 3)) { + this.logger.error(`Job ${job.id} failed after ${job.attemptsMade} attempts`); + return { success: false }; + } + throw new Error(`Job ${job.id} failed, retrying`); + } catch (e) { + this.logger.error(`Job ${job.id} failed (attempts=${job.attemptsMade}) with error: ${e}`); + throw e; + } finally { + // do some stuff + } + } + + // eslint-disable-next-line class-methods-use-this + @OnWorkerEvent('completed') + onCompleted() { + // do some stuff + } + + private async crawlBlockList(txHash: Hash, epoch: string, blockList: bigint[]): Promise { + const txReceiptPromises: Promise[] = blockList.map(async (blockNumber) => { + const blockHash = await this.blockchainService.getBlockHash(blockNumber); + const block = await this.blockchainService.getBlock(blockHash); + const txInfo = block.block.extrinsics.find((extrinsic) => extrinsic.hash.toString() === txHash.toString()); + this.logger.debug(`Extrinsics: ${block.block.extrinsics[0]}`); + + if (txInfo !== undefined) { + this.logger.verbose(`Found tx ${txHash} in block ${blockNumber}`); + const at = await this.blockchainService.api.at(blockHash.toHex()); + const events = await at.query.system.events(); + events.subscribe((records) => { + records.forEach(async (record) => { + const { event } = record; + const eventName = event.section; + const { method } = event; + const { data } = event; + this.logger.debug(`Received event: ${eventName} ${method} ${data}`); + if (eventName.search('capacity') !== -1 && method.search('Withdrawn') !== -1) { + const capacityWithDrawn = BigInt(data[1].toString()); + this.logger.debug(`Capacity withdrawn: ${capacityWithDrawn}`); + this.setEpochCapacity(epoch, capacityWithDrawn); + } + }); + }); + return blockHash; + } + return undefined; + }); + + const results = await Promise.all(txReceiptPromises); + const result = results.find((blockHash) => blockHash !== undefined); + return result; + } + + private async setEpochCapacity(epoch: string, capacityWithdrew: bigint) { + const epochCapacityKey = `epochCapacity:${epoch}`; + + try { + const epochCapacity = BigInt((await this.cacheManager.get(epochCapacityKey)) ?? 0); + const newEpochCapacity = epochCapacity + capacityWithdrew; + + const epochDurationBlocks = await this.blockchainService.getCurrentEpochLength(); + const epochDuration = epochDurationBlocks * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; + + await this.cacheManager.setex(epochCapacityKey, epochDuration, newEpochCapacity.toString()); + } catch (error) { + this.logger.error(`Error setting epoch capacity: ${error}`); + + throw error; + } + } +} diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts index 8859505d..79a68e91 100644 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -1,65 +1,49 @@ import { Injectable, Logger } from '@nestjs/common'; -import { EventEmitter2 } from '@nestjs/event-emitter'; import { KeyringPair } from '@polkadot/keyring/types'; import { ISubmittableResult } from '@polkadot/types/types'; import { SubmittableExtrinsic } from '@polkadot/api-base/types'; -import { InjectQueue } from '@nestjs/bullmq'; import { Hash } from '@polkadot/types/interfaces'; import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IPublisherJob } from '../interfaces/publisher-job.interface'; import { createKeys } from '../../../../libs/common/src/blockchain/create-keys'; -import { IStatusMonitorJob } from '../interfaces/status-monitor.interface'; -import { QueueConstants } from '../../../../libs/common/src'; @Injectable() export class IPFSPublisher { private logger: Logger; constructor( - @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue, private configService: ConfigService, private blockchainService: BlockchainService, - private eventEmitter: EventEmitter2, ) { this.logger = new Logger(IPFSPublisher.name); } - public async publish(message: IPublisherJob): Promise<{ [key: string]: bigint }> { + public async publish(message: IPublisherJob): Promise { this.logger.debug(JSON.stringify(message)); const providerKeys = createKeys(this.configService.getProviderAccountSeedPhrase()); const tx = this.blockchainService.createExtrinsicCall({ pallet: 'messages', extrinsic: 'addIpfsMessage' }, message.schemaId, message.data.cid, message.data.payloadLength); - return this.processSingleBatch(message.id, providerKeys, tx); + return this.processSingleBatch(providerKeys, tx); } - async processSingleBatch(jobId: string, providerKeys: KeyringPair, tx: SubmittableExtrinsic<'rxjs', ISubmittableResult>): Promise<{ [key: string]: bigint }> { + async processSingleBatch(providerKeys: KeyringPair, tx: SubmittableExtrinsic<'rxjs', ISubmittableResult>): Promise { this.logger.debug(`Submitting tx of size ${tx.length}`); try { - const currrentEpoch = await this.blockchainService.getCurrentCapacityEpoch(); - const [txHash, eventMap] = await this.blockchainService - .createExtrinsic({ pallet: 'frequencyTxPayment', extrinsic: 'payWithCapacity' }, { eventPallet: 'messages', event: 'MessagesStored' }, providerKeys, tx) - .signAndSend(); + const ext = await this.blockchainService.createExtrinsic( + { pallet: 'frequencyTxPayment', extrinsic: 'payWithCapacity' }, + { eventPallet: 'messages', event: 'MessagesStored' }, + providerKeys, + tx, + ); + const [txHash] = await ext.signAndSend(); if (!txHash) { throw new Error('Tx hash is undefined'); } - - const capacityWithDrawn = BigInt(eventMap['capacity.CapacityWithdrawn'].data[1].toString()); - - this.sendJobToTxReceiptQueue(jobId, txHash); - this.logger.debug(`Batch processed, capacity withdrawn: ${capacityWithDrawn}`); - return { [currrentEpoch.toString()]: capacityWithDrawn }; + this.logger.debug(`Tx hash: ${txHash}`); + return txHash; } catch (e) { this.logger.error(`Error processing batch: ${e}`); throw e; } } - - async sendJobToTxReceiptQueue(jobId: any, txHash: Hash): Promise { - const job: IStatusMonitorJob = { - id: txHash.toString(), - txHash, - publisherJobId: jobId, - }; - await this.txReceiptQueue.add(txHash.toString(), job, { removeOnComplete: true, removeOnFail: true }); - } } diff --git a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts index 0d6401ee..4e6cf3e8 100644 --- a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts +++ b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts @@ -66,7 +66,7 @@ import { QueueConstants } from '../../../../libs/common/src'; { name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, defaultJobOptions: { - attempts: 1, + attempts: 3, backoff: { type: 'exponential', }, diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts index b547c272..5d34b10a 100644 --- a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts +++ b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts @@ -6,12 +6,14 @@ import Redis from 'ioredis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; import { MILLISECONDS_PER_SECOND } from 'time-constants'; +import { BlockHash, Hash } from '@polkadot/types/interfaces'; import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IPublisherJob } from '../interfaces/publisher-job.interface'; import { IPFSPublisher } from './ipfs.publisher'; import { CAPACITY_EPOCH_TIMEOUT_NAME, SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; import { QueueConstants } from '../../../../libs/common/src'; +import { ITxMonitorJob } from '../interfaces/status-monitor.interface'; @Injectable() @Processor(QueueConstants.PUBLISH_QUEUE_NAME, { @@ -24,6 +26,7 @@ export class PublishingService extends WorkerHost implements OnApplicationBootst constructor( @InjectRedis() private cacheManager: Redis, + @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue, @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, private blockchainService: BlockchainService, private configService: ConfigService, @@ -50,16 +53,16 @@ export class PublishingService extends WorkerHost implements OnApplicationBootst async process(job: Job): Promise { this.logger.log(`Processing job ${job.id} of type ${job.name}`); try { - const totalCapacityUsed = await this.ipfsPublisher.publish(job.data); - await this.setEpochCapacity(totalCapacityUsed); - + const currentBlockHash = await this.blockchainService.getLatestFinalizedBlockHash(); + const currentCapacityEpoch = await this.blockchainService.getCurrentCapacityEpoch(); + const txHash = await this.ipfsPublisher.publish(job.data); + await this.sendJobToTxReceiptQueue(job.data, txHash, currentBlockHash, currentCapacityEpoch.toString()); this.logger.verbose(`Successfully completed job ${job.id}`); return { success: true }; } catch (e) { - this.logger.error(`Job ${job.id} failed (attempts=${job.attemptsMade})`); + this.logger.error(`Job ${job.id} failed (attempts=${job.attemptsMade})error: ${e}`); if (e instanceof Error && e.message.includes('Inability to pay some fees')) { this.eventEmitter.emit('capacity.exhausted'); - // TODO: revisit this logic } throw e; } finally { @@ -67,24 +70,17 @@ export class PublishingService extends WorkerHost implements OnApplicationBootst } } - private async setEpochCapacity(totalCapacityUsed: { [key: string]: bigint }): Promise { - Object.entries(totalCapacityUsed).forEach(async ([epoch, capacityUsed]) => { - const epochCapacityKey = `epochCapacity:${epoch}`; - - try { - const epochCapacity = BigInt((await this.cacheManager.get(epochCapacityKey)) ?? 0); - const newEpochCapacity = epochCapacity + capacityUsed; - - const epochDurationBlocks = await this.blockchainService.getCurrentEpochLength(); - const epochDuration = epochDurationBlocks * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; - - await this.cacheManager.setex(epochCapacityKey, epochDuration, newEpochCapacity.toString()); - } catch (error) { - this.logger.error(`Error setting epoch capacity: ${error}`); - - throw error; - } - }); + async sendJobToTxReceiptQueue(jobData: IPublisherJob, txHash: Hash, lastFinalizedBlockHash: BlockHash, epoch: string): Promise { + const job: ITxMonitorJob = { + id: txHash.toString(), + epoch, + lastFinalizedBlockHash, + txHash, + referencePublishJob: jobData, + }; + // add a delay of 1 block to allow the tx reciept to go through before checking + const delay = 1 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; + await this.txReceiptQueue.add(`Tx Receipt Job - ${job.id}`, job, { jobId: job.id, removeOnFail: false, removeOnComplete: 1000, delay }); } private async checkCapacity(): Promise { diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts index f330ad0c..e3159eb9 100644 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -10,7 +10,7 @@ import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain import { BatchAnnouncementService } from './batch_announcer/batch.announcer.service'; import { BatchAnnouncerModule } from './batch_announcer/batch.announcer.module'; import { StatusMonitorModule } from './monitor/status.monitor.module'; -import { StatusMonitoringService } from './monitor/status.monitor.service'; +import { TxStatusMonitoringService } from './monitor/tx.status.monitor.service'; import { AssetProcessorModule } from './asset_processor/asset.processor.module'; import { AssetProcessorService } from './asset_processor/asset.processor.service'; import { RequestProcessorModule } from './request_processor/request.processor.module'; @@ -60,6 +60,6 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; RequestProcessorModule, BatchingProcessorModule, ], - providers: [BatchAnnouncementService, ConfigService, WorkerService, PublishingService, StatusMonitoringService, AssetProcessorService, RequestProcessorService], + providers: [BatchAnnouncementService, ConfigService, WorkerService, PublishingService, TxStatusMonitoringService, AssetProcessorService, RequestProcessorService], }) export class WorkerModule {} diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts b/services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts index 31ec1057..7172599b 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts @@ -18,7 +18,6 @@ export namespace BlockchainConstants { rpcPallet: string; rpc: string; } - const PALLET_FREQ_TX_PYMT = 'frequencyTxPayment'; const PALLET_STATEFUL_STORAGE = 'statefulStorage'; @@ -26,4 +25,17 @@ export namespace BlockchainConstants { const EX_UPSERT_PAGE = 'upsertPage'; const PAY_WITH_CAPACITY_BATCH: IExtrinsicCall = { pallet: PALLET_FREQ_TX_PYMT, extrinsic: EX_PAY_CAPACITY_BATCH }; + + /** + * The number of blocks to crawl for a given job + * @type {number} + * @memberof BlockchainConstants + * @static + * @readonly + * @public + * @constant + * @description + * The number of blocks to crawl for a given job + */ + export const NUMBER_BLOCKS_TO_CRAWL = 32n; // TODO: take from tx, keeping it constant to default tx mortality } diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index d800286c..ce163677 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -4,11 +4,12 @@ import { ApiPromise, ApiRx, HttpProvider, WsProvider } from '@polkadot/api'; import { firstValueFrom } from 'rxjs'; import { options } from '@frequency-chain/api-augment'; import { KeyringPair } from '@polkadot/keyring/types'; -import { BlockHash, BlockNumber } from '@polkadot/types/interfaces'; +import { BlockHash, BlockNumber, SignedBlock } from '@polkadot/types/interfaces'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; import { u32, Option } from '@polkadot/types'; import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo, PalletSchemasSchema } from '@polkadot/types/lookup'; +import { Hash } from 'crypto'; import { ConfigService } from '../config/config.service'; import { Extrinsic } from './extrinsic'; @@ -60,6 +61,18 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS return this.apiPromise.rpc.chain.getBlockHash(block); } + public getBlock(block: BlockHash): Promise { + return this.apiPromise.rpc.chain.getBlock(block); + } + + public async getLatestFinalizedBlockHash(): Promise { + return (await this.apiPromise.rpc.chain.getFinalizedHead()) as BlockHash; + } + + public async getLatestFinalizedBlockNumber(): Promise { + return (await this.apiPromise.rpc.chain.getBlock()).block.header.number.toBigInt(); + } + public async getBlockNumberForHash(hash: string): Promise { const block = await this.apiPromise.rpc.chain.getBlock(hash); if (block) { diff --git a/services/content-watcher/libs/common/src/blockchain/extrinsic.ts b/services/content-watcher/libs/common/src/blockchain/extrinsic.ts index 7472473c..d40cdc13 100644 --- a/services/content-watcher/libs/common/src/blockchain/extrinsic.ts +++ b/services/content-watcher/libs/common/src/blockchain/extrinsic.ts @@ -63,19 +63,17 @@ export class Extrinsic { - return firstValueFrom(this.extrinsic.signAndSend(this.keys, { nonce }).pipe(filter(({ status }) => status.isInBlock || status.isFinalized))).then( - ({ status, events, txHash }) => { - if (status.isInBlock || status.isFinalized) { - const eventMap: EventMap = {}; - events.forEach((record: EventRecord) => { - const { event } = record; - eventMap[eventKey(event)] = event; - }); - return [txHash, eventMap]; - } - throw new Error(`Transaction failed to finalize: ${txHash}`); - }, - ); + return firstValueFrom(this.extrinsic.signAndSend(this.keys, { nonce })).then(({ status, events, txHash }) => { + if (status.isFinalized || status.isInBlock) { + const eventMap: EventMap = {}; + events.forEach((record: EventRecord) => { + const { event } = record; + eventMap[eventKey(event)] = event; + }); + return [txHash, eventMap]; + } + return [txHash, {}]; + }); } public getCall(): Call { From 2d6d1b6bb1f33bb1d09edcc75d8dc720d8a48d8b Mon Sep 17 00:00:00 2001 From: Aramik Date: Mon, 2 Oct 2023 13:35:41 -0700 Subject: [PATCH 028/137] adding graceful shutdowns (#71) * adding graceful shutdowns * removed extra log * removed leftover timeout --- .../apps/worker/src/BaseConsumer.ts | 58 +++++++++++++++++++ .../asset.processor.service.ts | 8 +-- .../batch.announcer.service.ts | 22 ++----- .../batching.processor.service.ts | 6 +- .../workers/broadcast.worker.ts | 8 +-- .../workers/profile.worker.ts | 12 ++-- .../workers/reaction.worker.ts | 8 +-- .../workers/reply.worker.ts | 12 ++-- .../workers/tombstone.worker.ts | 12 ++-- .../workers/update.worker.ts | 8 +-- .../apps/worker/src/consumer.ts | 18 ------ .../apps/worker/src/event.listener.ts | 19 ------ .../content-watcher/apps/worker/src/main.ts | 4 +- .../src/monitor/tx.status.monitor.service.ts | 31 ++-------- .../src/publisher/publishing.service.ts | 16 ++--- .../request.processor.service.ts | 16 ++--- .../apps/worker/src/worker.module.ts | 3 +- .../apps/worker/src/worker.service.ts | 9 --- .../libs/common/src/utils/processing.ts | 8 +++ 19 files changed, 128 insertions(+), 150 deletions(-) create mode 100644 services/content-watcher/apps/worker/src/BaseConsumer.ts delete mode 100644 services/content-watcher/apps/worker/src/consumer.ts delete mode 100644 services/content-watcher/apps/worker/src/event.listener.ts delete mode 100644 services/content-watcher/apps/worker/src/worker.service.ts create mode 100644 services/content-watcher/libs/common/src/utils/processing.ts diff --git a/services/content-watcher/apps/worker/src/BaseConsumer.ts b/services/content-watcher/apps/worker/src/BaseConsumer.ts new file mode 100644 index 00000000..52315b16 --- /dev/null +++ b/services/content-watcher/apps/worker/src/BaseConsumer.ts @@ -0,0 +1,58 @@ +import { OnWorkerEvent, WorkerHost } from '@nestjs/bullmq'; +import { Logger, OnModuleDestroy } from '@nestjs/common'; +import { Job, Worker } from 'bullmq'; +import { ProcessingUtils } from '../../../libs/common/src/utils/processing'; + +export abstract class BaseConsumer extends WorkerHost implements OnModuleDestroy { + protected logger: Logger; + + private actives: Set; + + protected constructor() { + super(); + this.logger = new Logger(this.constructor.name); + this.actives = new Set(); + } + + trackJob(jobId: string) { + this.actives.add(jobId); + } + + unTrackJob(jobId: string) { + this.actives.delete(jobId); + } + + async onModuleDestroy(): Promise { + await this.worker?.close(false); + let maxWaitMs = ProcessingUtils.MAX_WAIT_FOR_GRACE_FULL_SHUTDOWN_MS; + while (this.actives.size > 0 && maxWaitMs > 0) { + // eslint-disable-next-line no-await-in-loop + await ProcessingUtils.delay(ProcessingUtils.DELAY_TO_CHECK_FOR_SHUTDOWN_MS); + maxWaitMs -= ProcessingUtils.DELAY_TO_CHECK_FOR_SHUTDOWN_MS; + } + } + + /** + * any method overriding this method should call this method directly, so we are able to track the executing jobs + */ + @OnWorkerEvent('active') + onActive(job: Job) { + this.trackJob(job.id!); + } + + /** + * any method overriding this method should call this method directly, so we are able to track the executing jobs + */ + @OnWorkerEvent('completed') + onCompleted(job: Job) { + this.unTrackJob(job.id!); + } + + /** + * any method overriding this method should call this method directly, so we are able to track the executing jobs + */ + @OnWorkerEvent('failed') + onFailed(job: Job) { + this.unTrackJob(job.id!); + } +} diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts index fcb77257..1f841456 100644 --- a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts +++ b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts @@ -7,19 +7,17 @@ import { ConfigService } from '../../../../libs/common/src/config/config.service import { QueueConstants } from '../../../../libs/common/src'; import { IAssetJob } from '../../../../libs/common/src/interfaces/asset-job.interface'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; +import { BaseConsumer } from '../BaseConsumer'; @Injectable() @Processor(QueueConstants.ASSET_QUEUE_NAME) -export class AssetProcessorService extends WorkerHost { - private logger: Logger; - +export class AssetProcessorService extends BaseConsumer { constructor( @InjectRedis() private redis: Redis, private configService: ConfigService, private ipfsService: IpfsService, ) { super(); - this.logger = new Logger(this.constructor.name); } async process(job: Job): Promise { @@ -45,5 +43,7 @@ export class AssetProcessorService extends WorkerHost { const secondsToExpire = Math.max(0, expectedSecondsToExpire - secondsPassed); const result = await this.redis.pipeline().expire(job.data.contentLocation, secondsToExpire, 'LT').expire(job.data.metadataLocation, secondsToExpire, 'LT').exec(); this.logger.debug(result); + // calling in the end for graceful shutdowns + super.onCompleted(job); } } diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts index b999f6d9..8d73c83b 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts @@ -10,14 +10,13 @@ import { BatchAnnouncer } from './batch.announcer'; import { CAPACITY_EPOCH_TIMEOUT_NAME } from '../../../../libs/common/src/constants'; import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; import { QueueConstants } from '../../../../libs/common/src'; +import { BaseConsumer } from '../BaseConsumer'; @Injectable() @Processor(QueueConstants.BATCH_QUEUE_NAME, { concurrency: 2, }) -export class BatchAnnouncementService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { - private logger: Logger; - +export class BatchAnnouncementService extends BaseConsumer implements OnModuleDestroy { constructor( @InjectRedis() private cacheManager: Redis, @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, @@ -27,26 +26,23 @@ export class BatchAnnouncementService extends WorkerHost implements OnApplicatio private eventEmitter: EventEmitter2, ) { super(); - this.logger = new Logger(this.constructor.name); - } - - public async onApplicationBootstrap() { - this.logger.log('onApplicationBootstrap'); } - public onModuleDestroy() { + async onModuleDestroy(): Promise { try { this.schedulerRegistry.deleteTimeout(CAPACITY_EPOCH_TIMEOUT_NAME); } catch (e) { // 💀 // } + // calling in the end for graceful shutdowns + await super.onModuleDestroy(); } async process(job: Job): Promise { this.logger.log(`Processing job ${job.id} of type ${job.name}`); try { const publisherJob = await this.ipfsPublisher.announce(job.data); - + // eslint-disable-next-line no-promise-executor-return await this.publishQueue.add(publisherJob.id, publisherJob); this.logger.log(`Completed job ${job.id} of type ${job.name}`); return job.data; @@ -55,10 +51,4 @@ export class BatchAnnouncementService extends WorkerHost implements OnApplicatio throw e; } } - - // eslint-disable-next-line class-methods-use-this - @OnWorkerEvent('completed') - onCompleted() { - // do some stuff - } } diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts index 9518de57..07c05bd2 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts @@ -154,6 +154,10 @@ export class BatchingProcessorService { private addBatchTimeout(queueName: string, batchId: string, timeoutMs: number) { const timeoutHandler = setTimeout(async () => this.closeBatch(queueName, batchId, true), timeoutMs); - this.schedulerRegistry.addTimeout(BatchingProcessorService.getTimeoutName(queueName, batchId), timeoutHandler); + const timeoutName = BatchingProcessorService.getTimeoutName(queueName, batchId); + if (this.schedulerRegistry.doesExist('timeout', timeoutName)) { + this.schedulerRegistry.deleteTimeout(timeoutName); + } + this.schedulerRegistry.addTimeout(timeoutName, timeoutHandler); } } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts index a948e5c5..c9881cd6 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts @@ -4,15 +4,13 @@ import { Job } from 'bullmq'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; +import { BaseConsumer } from '../../BaseConsumer'; @Injectable() @Processor(QueueConstants.BROADCAST_QUEUE_NAME, { concurrency: 2 }) -export class BroadcastWorker extends WorkerHost implements OnApplicationBootstrap { - private logger: Logger; - +export class BroadcastWorker extends BaseConsumer implements OnApplicationBootstrap { constructor(private batchingProcessorService: BatchingProcessorService) { super(); - this.logger = new Logger(this.constructor.name); } async onApplicationBootstrap() { @@ -26,5 +24,7 @@ export class BroadcastWorker extends WorkerHost implements OnApplicationBootstra @OnWorkerEvent('completed') async onCompleted(job: Job) { await this.batchingProcessorService.onCompleted(job, QueueConstants.BROADCAST_QUEUE_NAME); + // calling in the end for graceful shutdowns + super.onCompleted(job); } } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts index ab229830..060a80e9 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts @@ -1,18 +1,16 @@ -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Processor, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; +import { BaseConsumer } from '../../BaseConsumer'; @Injectable() @Processor(QueueConstants.PROFILE_QUEUE_NAME, { concurrency: 2 }) -export class ProfileWorker extends WorkerHost implements OnApplicationBootstrap { - private logger: Logger; - +export class ProfileWorker extends BaseConsumer implements OnApplicationBootstrap { constructor(private batchingProcessorService: BatchingProcessorService) { super(); - this.logger = new Logger(this.constructor.name); } async onApplicationBootstrap() { @@ -26,5 +24,7 @@ export class ProfileWorker extends WorkerHost implements OnApplicationBootstrap @OnWorkerEvent('completed') async onCompleted(job: Job) { await this.batchingProcessorService.onCompleted(job, QueueConstants.PROFILE_QUEUE_NAME); + // calling in the end for graceful shutdowns + super.onCompleted(job); } } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts index 9b225a22..2fcf38ba 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts @@ -4,15 +4,13 @@ import { Job } from 'bullmq'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; +import { BaseConsumer } from '../../BaseConsumer'; @Injectable() @Processor(QueueConstants.REACTION_QUEUE_NAME, { concurrency: 2 }) -export class ReactionWorker extends WorkerHost implements OnApplicationBootstrap { - private logger: Logger; - +export class ReactionWorker extends BaseConsumer implements OnApplicationBootstrap { constructor(private batchingProcessorService: BatchingProcessorService) { super(); - this.logger = new Logger(this.constructor.name); } async onApplicationBootstrap() { @@ -26,5 +24,7 @@ export class ReactionWorker extends WorkerHost implements OnApplicationBootstrap @OnWorkerEvent('completed') async onCompleted(job: Job) { await this.batchingProcessorService.onCompleted(job, QueueConstants.REACTION_QUEUE_NAME); + // calling in the end for graceful shutdowns + super.onCompleted(job); } } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts index e7d4469f..780da5a2 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts @@ -1,18 +1,16 @@ -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Processor, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; +import { BaseConsumer } from '../../BaseConsumer'; @Injectable() @Processor(QueueConstants.REPLY_QUEUE_NAME, { concurrency: 2 }) -export class ReplyWorker extends WorkerHost implements OnApplicationBootstrap { - private logger: Logger; - +export class ReplyWorker extends BaseConsumer implements OnApplicationBootstrap { constructor(private batchingProcessorService: BatchingProcessorService) { super(); - this.logger = new Logger(this.constructor.name); } async onApplicationBootstrap() { @@ -26,5 +24,7 @@ export class ReplyWorker extends WorkerHost implements OnApplicationBootstrap { @OnWorkerEvent('completed') async onCompleted(job: Job) { await this.batchingProcessorService.onCompleted(job, QueueConstants.REPLY_QUEUE_NAME); + // calling in the end for graceful shutdowns + super.onCompleted(job); } } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts index 6a287084..6d15265d 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts @@ -1,18 +1,16 @@ -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Processor, OnWorkerEvent } from '@nestjs/bullmq'; +import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; import { Job } from 'bullmq'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; +import { BaseConsumer } from '../../BaseConsumer'; @Injectable() @Processor(QueueConstants.TOMBSTONE_QUEUE_NAME, { concurrency: 2 }) -export class TombstoneWorker extends WorkerHost implements OnApplicationBootstrap { - private logger: Logger; - +export class TombstoneWorker extends BaseConsumer implements OnApplicationBootstrap { constructor(private batchingProcessorService: BatchingProcessorService) { super(); - this.logger = new Logger(this.constructor.name); } async onApplicationBootstrap() { @@ -26,5 +24,7 @@ export class TombstoneWorker extends WorkerHost implements OnApplicationBootstra @OnWorkerEvent('completed') async onCompleted(job: Job) { await this.batchingProcessorService.onCompleted(job, QueueConstants.TOMBSTONE_QUEUE_NAME); + // calling in the end for graceful shutdowns + super.onCompleted(job); } } diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts index ca8a70ae..d50bd0e4 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts @@ -4,15 +4,13 @@ import { Job } from 'bullmq'; import { QueueConstants } from '../../../../../libs/common/src'; import { BatchingProcessorService } from '../batching.processor.service'; import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; +import { BaseConsumer } from '../../BaseConsumer'; @Injectable() @Processor(QueueConstants.UPDATE_QUEUE_NAME, { concurrency: 2 }) -export class UpdateWorker extends WorkerHost implements OnApplicationBootstrap { - private logger: Logger; - +export class UpdateWorker extends BaseConsumer implements OnApplicationBootstrap { constructor(private batchingProcessorService: BatchingProcessorService) { super(); - this.logger = new Logger(this.constructor.name); } async onApplicationBootstrap() { @@ -26,5 +24,7 @@ export class UpdateWorker extends WorkerHost implements OnApplicationBootstrap { @OnWorkerEvent('completed') async onCompleted(job: Job) { await this.batchingProcessorService.onCompleted(job, QueueConstants.UPDATE_QUEUE_NAME); + // calling in the end for graceful shutdowns + super.onCompleted(job); } } diff --git a/services/content-watcher/apps/worker/src/consumer.ts b/services/content-watcher/apps/worker/src/consumer.ts deleted file mode 100644 index 23fb3fee..00000000 --- a/services/content-watcher/apps/worker/src/consumer.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; -import { Job } from 'bullmq'; - -@Processor('exampleQueue', { - concurrency: 2, -}) -export class ExampleConsumer extends WorkerHost { - // eslint-disable-next-line class-methods-use-this - async process(job: Job): Promise { - console.log(job.data); - } - - // eslint-disable-next-line class-methods-use-this - @OnWorkerEvent('completed') - onCompleted() { - // do some stuff - } -} diff --git a/services/content-watcher/apps/worker/src/event.listener.ts b/services/content-watcher/apps/worker/src/event.listener.ts deleted file mode 100644 index 8f9cd3cb..00000000 --- a/services/content-watcher/apps/worker/src/event.listener.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { QueueEventsListener, QueueEventsHost, OnQueueEvent } from '@nestjs/bullmq'; - -@QueueEventsListener('exampleQueue') -export class ExampleQueueEvents extends QueueEventsHost { - startTime: number; - - constructor() { - super(); - this.startTime = new Date().getTime(); - } - - @OnQueueEvent('drained') - onDrained({ jobId }: { jobId: string }) { - // do some stuff - - const elapsed = new Date().getTime(); - console.log((elapsed - this.startTime) / 1000); - } -} diff --git a/services/content-watcher/apps/worker/src/main.ts b/services/content-watcher/apps/worker/src/main.ts index 5663f684..a821ecfa 100644 --- a/services/content-watcher/apps/worker/src/main.ts +++ b/services/content-watcher/apps/worker/src/main.ts @@ -1,10 +1,8 @@ import { NestFactory } from '@nestjs/core'; import { WorkerModule } from './worker.module'; -import { WorkerService } from './worker.service'; async function bootstrap() { const app = await NestFactory.createApplicationContext(WorkerModule); - const appService = app.get(WorkerService); - console.log(appService.getHello()); + app.enableShutdownHooks(); } bootstrap(); diff --git a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts index e5383bd0..bfc92e0e 100644 --- a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts +++ b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts @@ -1,26 +1,24 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; +import { Processor, InjectQueue } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; import { Job, Queue } from 'bullmq'; import Redis from 'ioredis'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { MILLISECONDS_PER_SECOND } from 'time-constants'; import { BlockHash, Hash } from '@polkadot/types/interfaces'; -import { map, tap, timeout } from 'rxjs'; import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { ITxMonitorJob } from '../interfaces/status-monitor.interface'; import { QueueConstants } from '../../../../libs/common/src'; import { SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; import { BlockchainConstants } from '../../../../libs/common/src/blockchain/blockchain-constants'; +import { BaseConsumer } from '../BaseConsumer'; @Injectable() @Processor(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, { concurrency: 2, }) -export class TxStatusMonitoringService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { - private logger: Logger; - +export class TxStatusMonitoringService extends BaseConsumer { constructor( @InjectRedis() private cacheManager: Redis, @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue, @@ -30,19 +28,6 @@ export class TxStatusMonitoringService extends WorkerHost implements OnApplicati private eventEmitter: EventEmitter2, ) { super(); - this.logger = new Logger(this.constructor.name); - } - - public async onApplicationBootstrap() { - this.logger.debug('Starting publishing service'); - } - - public onModuleDestroy() { - try { - this.logger.debug('Shutting down publishing service'); - } catch (e) { - // 🐂 // - } } async process(job: Job): Promise { @@ -64,7 +49,7 @@ export class TxStatusMonitoringService extends WorkerHost implements OnApplicati } // handle failure to find tx in block list after - // TODO - handle requeing of publish job in case of failure + // TODO - handle requeing of publish job in case of failure // Issue: https://github.com/AmplicaLabs/content-publishing-service/issues/18 if (!txBlockHash && job.attemptsMade >= (job.opts.attempts ?? 3)) { this.logger.error(`Job ${job.id} failed after ${job.attemptsMade} attempts`); @@ -79,12 +64,6 @@ export class TxStatusMonitoringService extends WorkerHost implements OnApplicati } } - // eslint-disable-next-line class-methods-use-this - @OnWorkerEvent('completed') - onCompleted() { - // do some stuff - } - private async crawlBlockList(txHash: Hash, epoch: string, blockList: bigint[]): Promise { const txReceiptPromises: Promise[] = blockList.map(async (blockNumber) => { const blockHash = await this.blockchainService.getBlockHash(blockNumber); diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts index 5d34b10a..c4637583 100644 --- a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts +++ b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts @@ -14,14 +14,13 @@ import { IPFSPublisher } from './ipfs.publisher'; import { CAPACITY_EPOCH_TIMEOUT_NAME, SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; import { QueueConstants } from '../../../../libs/common/src'; import { ITxMonitorJob } from '../interfaces/status-monitor.interface'; +import { BaseConsumer } from '../BaseConsumer'; @Injectable() @Processor(QueueConstants.PUBLISH_QUEUE_NAME, { concurrency: 2, }) -export class PublishingService extends WorkerHost implements OnApplicationBootstrap, OnModuleDestroy { - private logger: Logger; - +export class PublishingService extends BaseConsumer implements OnApplicationBootstrap, OnModuleDestroy { private capacityExhausted = false; constructor( @@ -35,19 +34,20 @@ export class PublishingService extends WorkerHost implements OnApplicationBootst private eventEmitter: EventEmitter2, ) { super(); - this.logger = new Logger(this.constructor.name); } public async onApplicationBootstrap() { await this.checkCapacity(); } - public onModuleDestroy() { + async onModuleDestroy(): Promise { try { this.schedulerRegistry.deleteTimeout(CAPACITY_EPOCH_TIMEOUT_NAME); } catch (e) { // 💀 // } + // calling in the end for graceful shutdowns + await super.onModuleDestroy(); } async process(job: Job): Promise { @@ -148,10 +148,4 @@ export class PublishingService extends WorkerHost implements OnApplicationBootst // ignore } } - - // eslint-disable-next-line class-methods-use-this - @OnWorkerEvent('completed') - onCompleted() { - // do some stuff - } } diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts index f02cf836..c507365a 100644 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts +++ b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts @@ -1,18 +1,17 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; -import { Injectable, Logger } from '@nestjs/common'; -import { DelayedError, Job, Queue } from 'bullmq'; +import { Processor } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { DelayedError, Job } from 'bullmq'; import Redis from 'ioredis'; import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IRequestJob, QueueConstants } from '../../../../libs/common/src'; import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; +import { BaseConsumer } from '../BaseConsumer'; @Injectable() @Processor(QueueConstants.REQUEST_QUEUE_NAME) -export class RequestProcessorService extends WorkerHost { - private logger: Logger; - +export class RequestProcessorService extends BaseConsumer { constructor( @InjectRedis() private cacheManager: Redis, private dsnpAnnouncementProcessor: DsnpAnnouncementProcessor, @@ -20,7 +19,6 @@ export class RequestProcessorService extends WorkerHost { private ipfsService: IpfsService, ) { super(); - this.logger = new Logger(this.constructor.name); } async process(job: Job): Promise { @@ -42,10 +40,6 @@ export class RequestProcessorService extends WorkerHost { } } - // eslint-disable-next-line class-methods-use-this - @OnWorkerEvent('completed') - onCompleted() {} - // eslint-disable-next-line class-methods-use-this private async delayJobAndIncrementAttempts(job: Job) { const { data } = job; diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts index e3159eb9..b70e14a2 100644 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ b/services/content-watcher/apps/worker/src/worker.module.ts @@ -5,7 +5,6 @@ import { EventEmitterModule } from '@nestjs/event-emitter'; import { RedisModule } from '@liaoliaots/nestjs-redis'; import { PublishingService } from './publisher/publishing.service'; import { PublisherModule } from './publisher/publisher.module'; -import { WorkerService } from './worker.service'; import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; import { BatchAnnouncementService } from './batch_announcer/batch.announcer.service'; import { BatchAnnouncerModule } from './batch_announcer/batch.announcer.module'; @@ -60,6 +59,6 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; RequestProcessorModule, BatchingProcessorModule, ], - providers: [BatchAnnouncementService, ConfigService, WorkerService, PublishingService, TxStatusMonitoringService, AssetProcessorService, RequestProcessorService], + providers: [BatchAnnouncementService, ConfigService, PublishingService, TxStatusMonitoringService, AssetProcessorService, RequestProcessorService], }) export class WorkerModule {} diff --git a/services/content-watcher/apps/worker/src/worker.service.ts b/services/content-watcher/apps/worker/src/worker.service.ts deleted file mode 100644 index bcc16565..00000000 --- a/services/content-watcher/apps/worker/src/worker.service.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class WorkerService { - // eslint-disable-next-line class-methods-use-this - getHello(): string { - return 'Hello World from Worker!'; - } -} diff --git a/services/content-watcher/libs/common/src/utils/processing.ts b/services/content-watcher/libs/common/src/utils/processing.ts new file mode 100644 index 00000000..060fa6b6 --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/processing.ts @@ -0,0 +1,8 @@ +export namespace ProcessingUtils { + export const MAX_WAIT_FOR_GRACE_FULL_SHUTDOWN_MS = 6 * 1000; + export const DELAY_TO_CHECK_FOR_SHUTDOWN_MS = 300; + export async function delay(ms): Promise { + // eslint-disable-next-line no-promise-executor-return + return new Promise((resolve) => setTimeout(resolve, ms)); + } +} From f2a41a47e1bb8ecab637dd55c9780f2c858ffa66 Mon Sep 17 00:00:00 2001 From: Aramik Date: Tue, 3 Oct 2023 09:32:32 -0700 Subject: [PATCH 029/137] setup local chain (#75) --- services/content-watcher/package.json | 3 +- .../content-watcher/scripts/local-init.cjs | 91 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 services/content-watcher/scripts/local-init.cjs diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index d4caabc0..ba3c46b1 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -23,7 +23,8 @@ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "pretest": "cp env.template .env", "test": "jest --coverage --verbose", - "test:e2e": "set -a ; . .env ; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles" + "test:e2e": "set -a ; . .env ; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", + "local:init": "node scripts/local-init.cjs" }, "repository": { "type": "git", diff --git a/services/content-watcher/scripts/local-init.cjs b/services/content-watcher/scripts/local-init.cjs new file mode 100644 index 00000000..2d518eff --- /dev/null +++ b/services/content-watcher/scripts/local-init.cjs @@ -0,0 +1,91 @@ +const { options } = require("@frequency-chain/api-augment"); +const { WsProvider, ApiPromise, Keyring } = require("@polkadot/api"); +const { deploy } = require("@dsnp/frequency-schemas/cli/deploy"); + +// Given a list of events, a section and a method, +// returns the first event with matching section and method. +const eventWithSectionAndMethod = (events, section, method) => { + const evt = events.find(({ event }) => event.section === section && event.method === method); + return evt?.event; +}; + +const main = async () => { + console.log("A quick script that will setup a clean localhost instance of Frequency for DSNP "); + + const providerUri = "ws://127.0.0.1:9944"; + const provider = new WsProvider(providerUri); + const api = await ApiPromise.create({ provider, throwOnConnect: true, ...options }); + const keys = new Keyring().addFromUri("//Alice", {}, "sr25519"); + + // Create alice msa + await new Promise((resolve, reject) => { + console.log("Creating an MSA..."); + api.tx.msa.create() + .signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error("ERROR: ", dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, "msa", "MsaCreated"); + if (evt) { + const id = evt?.data[0]; + console.log("SUCCESS: MSA Created:" + id); + resolve(); + } else { + console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); + reject(); + } + } + }); + }); + + // Create alice provider + await new Promise((resolve, reject) => { + console.log("Creating an Provider..."); + api.tx.msa.createProvider("alice") + .signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error("ERROR: ", dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, "msa", "ProviderCreated"); + if (evt) { + const id = evt?.data[0]; + console.log("SUCCESS: Provider Created:" + id); + resolve(); + } else { + console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); + reject(); + } + } + }); + }); + + // Alice provider get Capacity + await new Promise((resolve, reject) => { + console.log("Staking for Capacity..."); + api.tx.capacity.stake("1", 500_000 * Math.pow(8, 10)) + .signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error("ERROR: ", dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, "capacity", "Staked"); + if (evt) { + console.log("SUCCESS: Provider Staked:", evt.data.toHuman()); + resolve(); + } else { + console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); + reject(); + } + } + }); + }); + + // Deploy Schemas + await deploy(); + + console.log("Setup Complete!"); +} + +main().catch(console.error).finally(process.exit); From 7270fd14b5a3cf35f9919c95132ad8bea11445a9 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:44:15 -0500 Subject: [PATCH 030/137] Publish api swagger to gh pages (#76) * try swagger * upload and deploy docs * cleanup * prod swagger --- .../.github/workflows/build.yml | 24 + services/content-watcher/package-lock.json | 3456 +++++++++++++++++ services/content-watcher/package.json | 2 + services/content-watcher/swagger.yaml | 407 ++ 4 files changed, 3889 insertions(+) create mode 100644 services/content-watcher/swagger.yaml diff --git a/services/content-watcher/.github/workflows/build.yml b/services/content-watcher/.github/workflows/build.yml index 84a60224..27fda258 100644 --- a/services/content-watcher/.github/workflows/build.yml +++ b/services/content-watcher/.github/workflows/build.yml @@ -57,3 +57,27 @@ jobs: - name: License Check # List all the licenses and error out if it is not one of the supported licenses run: npx license-report --fields=name --fields=licenseType | jq 'map(select(.licenseType | IN("MIT", "Apache-2.0", "ISC", "BSD-3-Clause", "BSD-2-Clause", "(Apache-2.0 AND MIT)") | not)) | if length == 0 then halt else halt_error(1) end' + + publish: + runs-on: ubuntu-latest + needs: [build_Nest_js, test_jest, check_licenses] + steps: + - uses: actions/checkout@v3 + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + registry-url: 'https://registry.npmjs.org' + cache-dependency-path: package-lock.json + - name: Install dependencies + run: npm ci + - name: Generate Swagger UI + run: npm run generate-swagger-ui + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish generated swagger.html to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs \ No newline at end of file diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 1ef72d49..e199e128 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -77,6 +77,7 @@ "jest": "^29.5.0", "license-report": "^6.4.0", "prettier": "^3.0.2", + "redoc-cli": "^0.13.21", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "trace-unhandled": "^2.0.1", @@ -10616,6 +10617,3461 @@ "node": ">=4" } }, + "node_modules/redoc-cli": { + "version": "0.13.21", + "resolved": "https://registry.npmjs.org/redoc-cli/-/redoc-cli-0.13.21.tgz", + "integrity": "sha512-pjuPf0HkKqo9qtoHxMK4x5dhC/lJ08O0hO0rJISbSRCf19bPBjQ5lb2mHRu9j6vypTMltyaLtFIfVNveuyF5fQ==", + "dev": true, + "hasShrinkwrap": true, + "dependencies": { + "boxen": "5.1.2", + "chokidar": "^3.5.1", + "handlebars": "^4.7.7", + "mkdirp": "^1.0.4", + "mobx": "^6.3.2", + "node-libs-browser": "^2.2.1", + "react": "^17.0.1", + "react-dom": "^17.0.1", + "redoc": "2.0.0", + "styled-components": "^5.3.0", + "yargs": "^17.3.1" + }, + "bin": { + "redoc-cli": "index.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/redoc-cli/node_modules/@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.12.13" + } + }, + "node_modules/redoc-cli/node_modules/@babel/generator": { + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", + "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.14.2", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "node_modules/redoc-cli/node_modules/@babel/generator/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", + "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/redoc-cli/node_modules/@babel/helper-function-name": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", + "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.14.2" + } + }, + "node_modules/redoc-cli/node_modules/@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/redoc-cli/node_modules/@babel/helper-module-imports": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.13.12" + } + }, + "node_modules/redoc-cli/node_modules/@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.12.13" + } + }, + "node_modules/redoc-cli/node_modules/@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/@babel/highlight": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", + "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/redoc-cli/node_modules/@babel/parser": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", + "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/redoc-cli/node_modules/@babel/runtime": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", + "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/redoc-cli/node_modules/@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "node_modules/redoc-cli/node_modules/@babel/traverse": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", + "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.14.2", + "@babel/helper-function-name": "^7.14.2", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.14.2", + "@babel/types": "^7.14.2", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "node_modules/redoc-cli/node_modules/@babel/types": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", + "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.0", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/redoc-cli/node_modules/@emotion/is-prop-valid": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", + "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "dev": true, + "dependencies": { + "@emotion/memoize": "0.7.4" + } + }, + "node_modules/redoc-cli/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/@exodus/schemasafe": { + "version": "1.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.6.tgz", + "integrity": "sha512-dDnQizD94EdBwEj/fh3zPRa/HWCS9O5au2PuHhZBbuM3xWHxuaKzPBOEWze7Nn0xW68MIpZ7Xdyn1CoCpjKCuQ==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/redoc-cli/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/redoc-cli/node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/redoc-cli/node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/redoc-cli/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", + "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/redoc-cli/node_modules/@redocly/ajv": { + "version": "8.6.4", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", + "integrity": "sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/redoc-cli/node_modules/@redocly/openapi-core": { + "version": "1.0.0-beta.105", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.105.tgz", + "integrity": "sha512-8uYDMcqBOPhFgjRlg5uetW/E2uTVVRpk+YsJhaH78ZNuzBkQP5Waw5s8P8ym6myvHs5me8l5AdniY/ePLMT5xg==", + "dev": true, + "dependencies": { + "@redocly/ajv": "^8.6.4", + "@types/node": "^14.11.8", + "colorette": "^1.2.0", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "lodash.isequal": "^4.5.0", + "minimatch": "^5.0.1", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/redoc-cli/node_modules/@redocly/openapi-core/node_modules/@types/node": { + "version": "14.18.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.22.tgz", + "integrity": "sha512-qzaYbXVzin6EPjghf/hTdIbnVW1ErMx8rPzwRNJhlbyJhu2SyqlvjGOY/tbUt6VFyzg56lROcOeSQRInpt63Yw==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/@types/eslint": { + "version": "8.4.6", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", + "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/redoc-cli/node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/redoc-cli/node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/@types/mkdirp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", + "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", + "extraneous": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/redoc-cli/node_modules/@types/node": { + "version": "15.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", + "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "peer": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/redoc-cli/node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "peer": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/redoc-cli/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, + "peer": true + }, + "node_modules/redoc-cli/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, + "peer": true + }, + "node_modules/redoc-cli/node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/redoc-cli/node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/redoc-cli/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, + "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/redoc-cli/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, + "peer": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/redoc-cli/node_modules/ajv/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, + "peer": true + }, + "node_modules/redoc-cli/node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/redoc-cli/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/redoc-cli/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, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/redoc-cli/node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" + } + }, + "node_modules/redoc-cli/node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/redoc-cli/node_modules/babel-plugin-styled-components": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz", + "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-module-imports": "^7.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/redoc-cli/node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", + "dev": true + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/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==", + "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" + } + ] + }, + "node_modules/redoc-cli/node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/redoc-cli/node_modules/bn.js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", + "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/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/redoc-cli/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/redoc-cli/node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redoc-cli/node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/redoc-cli/node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/redoc-cli/node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/redoc-cli/node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/redoc-cli/node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/redoc-cli/node_modules/browserify-sign/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/redoc-cli/node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/redoc-cli/node_modules/browserslist": { + "version": "4.21.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", + "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001370", + "electron-to-chromium": "^1.4.202", + "node-releases": "^2.0.6", + "update-browserslist-db": "^1.0.5" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/redoc-cli/node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/redoc-cli/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, + "peer": true + }, + "node_modules/redoc-cli/node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/redoc-cli/node_modules/camelize": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/caniuse-lite": { + "version": "1.0.30001390", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz", + "integrity": "sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ], + "peer": true + }, + "node_modules/redoc-cli/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, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/redoc-cli/node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/redoc-cli/node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/redoc-cli/node_modules/classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/redoc-cli/node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/redoc-cli/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, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/redoc-cli/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "node_modules/redoc-cli/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, + "peer": true + }, + "node_modules/redoc-cli/node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/core-js": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.0.tgz", + "integrity": "sha512-CVU1xvJEfJGhyCpBrzzzU1kjCfgsGUxhEvwUV2e/cOedYWHdmluamx+knDnmhqALddMG16fZvIqvs9aijsHHaA==", + "dev": true, + "hasInstallScript": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/redoc-cli/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/redoc-cli/node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/redoc-cli/node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/redoc-cli/node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/redoc-cli/node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/node_modules/css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "dev": true, + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/redoc-cli/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/redoc-cli/node_modules/decko": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", + "integrity": "sha1-/UPHNelnuAEzBohKVvvmZZlraBc=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/redoc-cli/node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/redoc-cli/node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/redoc-cli/node_modules/dompurify": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.5.tgz", + "integrity": "sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/electron-to-chromium": { + "version": "1.4.242", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.242.tgz", + "integrity": "sha512-nPdgMWtjjWGCtreW/2adkrB2jyHjClo9PtVhR6rW+oxa4E4Wom642Tn+5LslHP3XPL5MCpkn5/UEY60EXylNeQ==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/redoc-cli/node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/redoc-cli/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/redoc-cli/node_modules/enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/redoc-cli/node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/redoc-cli/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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/redoc-cli/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, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/redoc-cli/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, + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/redoc-cli/node_modules/esrecurse/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, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/redoc-cli/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/redoc-cli/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/redoc-cli/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, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/redoc-cli/node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/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, + "peer": true + }, + "node_modules/redoc-cli/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==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redoc-cli/node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/redoc-cli/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/redoc-cli/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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/redoc-cli/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, + "peer": true + }, + "node_modules/redoc-cli/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/node_modules/graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/redoc-cli/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/node_modules/hash-base/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/redoc-cli/node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/redoc-cli/node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/redoc-cli/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==", + "dev": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/redoc-cli/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==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/http2-client": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", + "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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" + } + ] + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/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, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redoc-cli/node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/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/redoc-cli/node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/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, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/redoc-cli/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, + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/redoc-cli/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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/redoc-cli/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, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/redoc-cli/node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/redoc-cli/node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/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==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dev": true, + "dependencies": { + "foreach": "^2.0.4" + } + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/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, + "peer": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/redoc-cli/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/redoc-cli/node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "dev": true + }, + "node_modules/redoc-cli/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, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/redoc-cli/node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha1-GA8fnr74sOY45BZq1S24eb6y/8U=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/marked": { + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.15.tgz", + "integrity": "sha512-esX5lPdTfG4p8LDkv+obbRCyOKzB+820ZZyMOXJZygZBHrH9b3xXR64X4kT3sPe9Nx8qQXbmcz6kFSMt4Nfk6Q==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/redoc-cli/node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/redoc-cli/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, + "peer": true + }, + "node_modules/redoc-cli/node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/redoc-cli/node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/redoc-cli/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==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/redoc-cli/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==", + "dev": true, + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/redoc-cli/node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/redoc-cli/node_modules/mobx": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.3.2.tgz", + "integrity": "sha512-xGPM9dIE1qkK9Nrhevp0gzpsmELKU4MFUJRORW/jqxVFIHHWIoQrjDjL8vkwoJYY3C2CeVJqgvl38hgKTalTWg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/redoc-cli/node_modules/mobx-react": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.2.1.tgz", + "integrity": "sha512-LZS99KFLn75VWDXPdRJhILzVQ7qLcRjQbzkK+wVs0Qg4kWw5hOI2USp7tmu+9zP9KYsVBmKyx2k/8cTTBfsymw==", + "dev": true, + "dependencies": { + "mobx-react-lite": "^3.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.1.0", + "react": "^16.8.0 || ^17" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/redoc-cli/node_modules/mobx-react-lite": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.2.3.tgz", + "integrity": "sha512-7exWp1FV0M9dP08H9PIeHlJqDw4IdkQVRMfLYaZFMmlbzSS6ZU6p/kx392KN+rVf81hH3IQYewvRGQ70oiwmbw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.1.0", + "react": "^16.8.0 || ^17" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/redoc-cli/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/redoc-cli/node_modules/node-fetch-h2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", + "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "dev": true, + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/redoc-cli/node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/redoc-cli/node_modules/node-readfiles": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", + "integrity": "sha1-271K8SE04uY1wkXvk//Pb2BnOl0=", + "dev": true, + "dependencies": { + "es6-promise": "^3.2.1" + } + }, + "node_modules/redoc-cli/node_modules/node-releases": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", + "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/node_modules/oas-kit-common": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", + "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "dev": true, + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/redoc-cli/node_modules/oas-linter": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", + "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", + "dev": true, + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/redoc-cli/node_modules/oas-resolver": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", + "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", + "dev": true, + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/redoc-cli/node_modules/oas-schema-walker": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", + "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", + "dev": true, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/redoc-cli/node_modules/oas-validator": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", + "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.9", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/redoc-cli/node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/node_modules/openapi-sampler": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.0.tgz", + "integrity": "sha512-2QfjK1oM9Sv0q82Ae1RrUe3yfFmAyjF548+6eAeb+h/cL1Uj51TW4UezraBEvwEdzoBgfo4AaTLVFGTKj+yYDw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.7", + "json-pointer": "0.6.2" + } + }, + "node_modules/redoc-cli/node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/redoc-cli/node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/redoc-cli/node_modules/perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/redoc-cli/node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/node_modules/polished": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.1.4.tgz", + "integrity": "sha512-Nq5Mbza+Auo7N3sQb1QMFaQiDO+4UexWuSGR7Cjb4Sw11SZIJcrrFtiZ+L0jT9MBsUsxDboHVASbCLbE1rnECg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.16.7" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/redoc-cli/node_modules/postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/redoc-cli/node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/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, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/redoc-cli/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==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/redoc-cli/node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/redoc-cli/node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/redoc-cli/node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/redoc-cli/node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/redoc-cli/node_modules/react": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/node_modules/react-dom": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "scheduler": "^0.20.2" + }, + "peerDependencies": { + "react": "17.0.2" + } + }, + "node_modules/redoc-cli/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true, + "peer": true + }, + "node_modules/redoc-cli/node_modules/react-tabs": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-3.2.3.tgz", + "integrity": "sha512-jx325RhRVnS9DdFbeF511z0T0WEqEoMl1uCE3LoZ6VaZZm7ytatxbum0B8bCTmaiV0KsU+4TtLGTGevCic7SWg==", + "dev": true, + "dependencies": { + "clsx": "^1.1.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0-0" + } + }, + "node_modules/redoc-cli/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/redoc-cli/node_modules/readable-stream/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 + }, + "node_modules/redoc-cli/node_modules/readable-stream/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, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/redoc-cli/node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redoc-cli/node_modules/redoc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0.tgz", + "integrity": "sha512-rU8iLdAkT89ywOkYk66Mr+IofqaMASlRvTew0dJvopCORMIPUcPMxjlJbJNC6wsn2vvMnpUFLQ/0ISDWn9BWag==", + "dev": true, + "dependencies": { + "@redocly/openapi-core": "^1.0.0-beta.104", + "classnames": "^2.3.1", + "decko": "^1.2.0", + "dompurify": "^2.2.8", + "eventemitter3": "^4.0.7", + "json-pointer": "^0.6.2", + "lunr": "^2.3.9", + "mark.js": "^8.11.1", + "marked": "^4.0.15", + "mobx-react": "^7.2.0", + "openapi-sampler": "^1.3.0", + "path-browserify": "^1.0.1", + "perfect-scrollbar": "^1.5.5", + "polished": "^4.1.3", + "prismjs": "^1.27.0", + "prop-types": "^15.7.2", + "react-tabs": "^3.2.2", + "slugify": "~1.4.7", + "stickyfill": "^1.1.1", + "style-loader": "^3.3.1", + "swagger2openapi": "^7.0.6", + "url-template": "^2.0.8" + }, + "engines": { + "node": ">=6.9", + "npm": ">=3.0.0" + }, + "peerDependencies": { + "core-js": "^3.1.4", + "mobx": "^6.0.4", + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0", + "styled-components": "^4.1.1 || ^5.1.1" + } + }, + "node_modules/redoc-cli/node_modules/redoc/node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/reftools": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", + "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", + "dev": true, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/redoc-cli/node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/redoc-cli/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" + } + ] + }, + "node_modules/redoc-cli/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 + }, + "node_modules/redoc-cli/node_modules/scheduler": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/redoc-cli/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "peer": true, + "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/redoc-cli/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/redoc-cli/node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/redoc-cli/node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/redoc-cli/node_modules/should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/redoc-cli/node_modules/should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/redoc-cli/node_modules/should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/redoc-cli/node_modules/should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/slugify": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", + "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/redoc-cli/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redoc-cli/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, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/redoc-cli/node_modules/stickyfill": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", + "integrity": "sha1-OUE/7p0CXHSn5ZzuyyN4TMDxfwI=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/redoc-cli/node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/redoc-cli/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, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/redoc-cli/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/redoc-cli/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/redoc-cli/node_modules/style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/redoc-cli/node_modules/styled-components": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.0.tgz", + "integrity": "sha512-bPJKwZCHjJPf/hwTJl6TbkSZg/3evha+XPEizrZUGb535jLImwDUdjTNxXqjjaASt2M4qO4AVfoHJNe3XB/tpQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^0.8.8", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/redoc-cli/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, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/node_modules/swagger2openapi": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", + "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "node-fetch": "^2.6.1", + "node-fetch-h2": "^2.3.0", + "node-readfiles": "^0.2.0", + "oas-kit-common": "^1.0.8", + "oas-resolver": "^2.5.6", + "oas-schema-walker": "^1.1.5", + "oas-validator": "^5.0.8", + "reftools": "^1.1.9", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "boast": "boast.js", + "oas-validate": "oas-validate.js", + "swagger2openapi": "swagger2openapi.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/redoc-cli/node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/redoc-cli/node_modules/terser": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", + "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/redoc-cli/node_modules/terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" + }, + "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/redoc-cli/node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/redoc-cli/node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "node_modules/redoc-cli/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": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/redoc-cli/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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/redoc-cli/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "node_modules/redoc-cli/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/redoc-cli/node_modules/uglify-js": { + "version": "3.13.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.9.tgz", + "integrity": "sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/redoc-cli/node_modules/update-browserslist-db": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", + "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/redoc-cli/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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/redoc-cli/node_modules/uri-js/node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/redoc-cli/node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/redoc-cli/node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/redoc-cli/node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "peer": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/redoc-cli/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "node_modules/redoc-cli/node_modules/webpack": { + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "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.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "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/redoc-cli/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, + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/redoc-cli/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/redoc-cli/node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redoc-cli/node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "node_modules/redoc-cli/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/redoc-cli/node_modules/wrap-ansi/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/redoc-cli/node_modules/wrap-ansi/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/redoc-cli/node_modules/wrap-ansi/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/redoc-cli/node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/redoc-cli/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/redoc-cli/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/redoc-cli/node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true + }, + "node_modules/redoc-cli/node_modules/yargs": { + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "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.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/redoc-cli/node_modules/yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "license": "Apache-2.0", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index ba3c46b1..1ef5304d 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -6,6 +6,7 @@ "scripts": { "build": "nest build", "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", + "generate-swagger-ui": "redoc-cli bundle swagger.yaml --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "start:api": "nest start api", "start:worker": "nest start worker", @@ -106,6 +107,7 @@ "jest": "^29.5.0", "license-report": "^6.4.0", "prettier": "^3.0.2", + "redoc-cli": "^0.13.21", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "trace-unhandled": "^2.0.1", diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml new file mode 100644 index 00000000..52ffaea0 --- /dev/null +++ b/services/content-watcher/swagger.yaml @@ -0,0 +1,407 @@ +openapi: 3.0.0 +paths: + "/api/health": + get: + operationId: ApiController_health + parameters: [] + responses: + '200': + description: '' + "/api/asset/upload": + put: + operationId: ApiController_assetUpload + parameters: [] + requestBody: + required: true + description: Asset files + content: + multipart/form-data: + schema: + "$ref": "#/components/schemas/FilesUploadDto" + responses: + '202': + description: '' + content: + application/json: + schema: + "$ref": "#/components/schemas/UploadResponseDto" + "/api/content/{userDsnpId}/broadcast": + post: + operationId: ApiController_broadcast + parameters: + - name: userDsnpId + required: true + in: path + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/BroadcastDto" + responses: + '202': + description: '' + content: + application/json: + schema: + "$ref": "#/components/schemas/AnnouncementResponseDto" + "/api/content/{userDsnpId}/reply": + post: + operationId: ApiController_reply + parameters: + - name: userDsnpId + required: true + in: path + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/ReplyDto" + responses: + '202': + description: '' + content: + application/json: + schema: + "$ref": "#/components/schemas/AnnouncementResponseDto" + "/api/content/{userDsnpId}/reaction": + post: + operationId: ApiController_reaction + parameters: + - name: userDsnpId + required: true + in: path + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/ReactionDto" + responses: + '202': + description: '' + content: + application/json: + schema: + "$ref": "#/components/schemas/AnnouncementResponseDto" + "/api/content/{userDsnpId}": + put: + operationId: ApiController_update + parameters: + - name: userDsnpId + required: true + in: path + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/UpdateDto" + responses: + '202': + description: '' + content: + application/json: + schema: + "$ref": "#/components/schemas/AnnouncementResponseDto" + delete: + operationId: ApiController_delete + parameters: + - name: userDsnpId + required: true + in: path + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/TombstoneDto" + responses: + '202': + description: '' + content: + application/json: + schema: + "$ref": "#/components/schemas/AnnouncementResponseDto" + "/api/profile/{userDsnpId}": + put: + operationId: ApiController_profile + parameters: + - name: userDsnpId + required: true + in: path + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/ProfileDto" + responses: + '202': + description: '' + content: + application/json: + schema: + "$ref": "#/components/schemas/AnnouncementResponseDto" +info: + title: Content Publishing Service API + description: Content Publishing Service API + version: '1.0' + contact: {} +tags: [] +servers: [] +components: + securitySchemes: + bearer: + scheme: bearer + bearerFormat: JWT + type: http + description: Enter JWT token + cookie: + type: apiKey + in: cookie + name: SESSION + schemas: + FilesUploadDto: + type: object + properties: + files: + type: array + items: + type: string + format: binary + required: + - files + UploadResponseDto: + type: object + properties: + assetIds: + type: array + items: + type: string + required: + - assetIds + AssetReferenceDto: + type: object + properties: + referenceId: + type: string + minLength: 1 + height: + type: number + minimum: 1 + width: + type: number + minimum: 1 + duration: + type: string + pattern: DURATION_REGEX + required: + - referenceId + AssetDto: + type: object + properties: + type: + type: string + enum: + - link + - image + - audio + - video + references: + type: array + items: + "$ref": "#/components/schemas/AssetReferenceDto" + name: + type: string + minLength: 1 + href: + type: string + minLength: 1 + required: + - type + TagDto: + type: object + properties: + type: + type: string + enum: + - mention + - hashtag + name: + type: string + minLength: 1 + mentionedId: + type: string + minLength: 1 + pattern: DSNP_USER_URI_REGEX + required: + - type + LocationDto: + type: object + properties: + name: + type: string + minLength: 1 + accuracy: + type: number + minimum: 0 + maximum: 100 + altitude: + type: number + latitude: + type: number + longitude: + type: number + radius: + type: number + minimum: 0 + units: + type: string + enum: + - cm + - m + - km + - inches + - feet + - miles + required: + - name + NoteActivityDto: + type: object + properties: + content: + type: string + minLength: 1 + published: + type: string + pattern: ISO8601_REGEX + assets: + type: array + items: + "$ref": "#/components/schemas/AssetDto" + name: + type: string + tag: + type: array + items: + "$ref": "#/components/schemas/TagDto" + location: + "$ref": "#/components/schemas/LocationDto" + required: + - content + - published + BroadcastDto: + type: object + properties: + content: + "$ref": "#/components/schemas/NoteActivityDto" + required: + - content + AnnouncementResponseDto: + type: object + properties: + referenceId: + type: string + required: + - referenceId + ReplyDto: + type: object + properties: + inReplyTo: + type: string + pattern: DSNP_CONTENT_URI_REGEX + content: + "$ref": "#/components/schemas/NoteActivityDto" + required: + - inReplyTo + - content + ReactionDto: + type: object + properties: + emoji: + type: string + minLength: 1 + pattern: DSNP_EMOJI_REGEX + apply: + type: number + minimum: 0 + maximum: 255 + inReplyTo: + type: string + pattern: DSNP_CONTENT_URI_REGEX + required: + - emoji + - apply + - inReplyTo + UpdateDto: + type: object + properties: + targetContentHash: + type: string + pattern: DSNP_CONTENT_HASH_REGEX + targetAnnouncementType: + type: string + enum: + - broadcast + - reply + content: + "$ref": "#/components/schemas/NoteActivityDto" + required: + - targetContentHash + - targetAnnouncementType + - content + TombstoneDto: + type: object + properties: + targetContentHash: + type: string + pattern: DSNP_CONTENT_HASH_REGEX + targetAnnouncementType: + type: string + enum: + - broadcast + - reply + required: + - targetContentHash + - targetAnnouncementType + ProfileActivityDto: + type: object + properties: + icon: + type: array + items: + "$ref": "#/components/schemas/AssetReferenceDto" + summary: + type: string + published: + type: string + pattern: ISO8601_REGEX + name: + type: string + tag: + type: array + items: + "$ref": "#/components/schemas/TagDto" + location: + "$ref": "#/components/schemas/LocationDto" + ProfileDto: + type: object + properties: + profile: + "$ref": "#/components/schemas/ProfileActivityDto" + required: + - profile From 1ab4304403744d02bda5a63401abdaf81bc5d831 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:08:29 -0500 Subject: [PATCH 031/137] Handling Errors from Blockchain (#72) * check for main events * set placeholder to handle errors * add callback * refactor * strong typing * lint * refactor * cleanup * setup handleMessagesFailure * use error name * lint * make callbacks generic * finalize some error handling * fail after set attempts else throw and retry * decouple callbacks * simplify logic * handle messages pallet error * finalize and fail on special cases * clean/lint * fail job on unknown --- .../src/monitor/tx.status.monitor.service.ts | 108 +++++++++--------- .../src/publisher/publishing.service.ts | 2 +- .../src/blockchain/blockchain.service.ts | 76 +++++++++++- 3 files changed, 126 insertions(+), 60 deletions(-) diff --git a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts index bfc92e0e..f5158d09 100644 --- a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts +++ b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts @@ -3,11 +3,9 @@ import { Processor, InjectQueue } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job, Queue } from 'bullmq'; import Redis from 'ioredis'; -import { EventEmitter2 } from '@nestjs/event-emitter'; import { MILLISECONDS_PER_SECOND } from 'time-constants'; -import { BlockHash, Hash } from '@polkadot/types/interfaces'; +import { RegistryError } from '@polkadot/types/types'; import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { ITxMonitorJob } from '../interfaces/status-monitor.interface'; import { QueueConstants } from '../../../../libs/common/src'; import { SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; @@ -21,11 +19,9 @@ import { BaseConsumer } from '../BaseConsumer'; export class TxStatusMonitoringService extends BaseConsumer { constructor( @InjectRedis() private cacheManager: Redis, - @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue, + @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue: Queue, @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, private blockchainService: BlockchainService, - private configService: ConfigService, - private eventEmitter: EventEmitter2, ) { super(); } @@ -38,82 +34,86 @@ export class TxStatusMonitoringService extends BaseConsumer { const previousKnownBlockNumber = (await this.blockchainService.getBlock(job.data.lastFinalizedBlockHash)).block.header.number.toBigInt(); const currentFinalizedBlockNumber = await this.blockchainService.getLatestFinalizedBlockNumber(); const blockList: bigint[] = []; + const blockDelay = 1 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; + for (let i = previousKnownBlockNumber; i <= currentFinalizedBlockNumber && i < previousKnownBlockNumber + numberBlocksToParse; i += 1n) { blockList.push(i); } - const txBlockHash = await this.crawlBlockList(job.data.txHash, txCapacityEpoch, blockList); + const txResult = await this.blockchainService.crawlBlockListForTx(job.data.txHash, blockList, [{ pallet: 'messages', event: 'MessageStored' }]); - if (txBlockHash) { - this.logger.verbose(`Successfully completed job ${job.id}`); - return { success: true }; + // if tx has not yet included in a block, throw error to retry till max attempts + if (!txResult.blockHash && !txResult.error) { + throw new Error(`Tx not found in block list, retrying (attempts=${job.attemptsMade})`); } - // handle failure to find tx in block list after - // TODO - handle requeing of publish job in case of failure - // Issue: https://github.com/AmplicaLabs/content-publishing-service/issues/18 - if (!txBlockHash && job.attemptsMade >= (job.opts.attempts ?? 3)) { - this.logger.error(`Job ${job.id} failed after ${job.attemptsMade} attempts`); - return { success: false }; + this.setEpochCapacity(txCapacityEpoch, BigInt(txResult.capacityWithDrawn ?? 0n)); + + if (txResult.error && job.attemptsMade <= (job.opts.attempts ?? 3)) { + this.logger.debug(`Error found in tx result: ${JSON.stringify(txResult.error)}`); + const errorReport = await this.handleMessagesFailure(job.data.id, txResult.error); + const failedError = new Error(`Job ${job.data.id} failed with error ${JSON.stringify(txResult.error)}`); + + if (errorReport.pause) { + await this.publishQueue.pause(); + } + + if (errorReport.retry) { + this.logger.debug(`Retrying job ${job.data.id}`); + await this.publishQueue.removeRepeatableByKey(job.data.referencePublishJob.id); + await this.publishQueue.add(job.data.referencePublishJob.id, job.data.referencePublishJob, { delay: blockDelay }); + } } - throw new Error(`Job ${job.id} failed, retrying`); + await this.txReceiptQueue.removeRepeatableByKey(job.data.id); + throw new Error(`Job ${job.data.id} failed with error ${JSON.stringify(txResult.error)}`); } catch (e) { - this.logger.error(`Job ${job.id} failed (attempts=${job.attemptsMade}) with error: ${e}`); + this.logger.error(e); throw e; } finally { // do some stuff } } - private async crawlBlockList(txHash: Hash, epoch: string, blockList: bigint[]): Promise { - const txReceiptPromises: Promise[] = blockList.map(async (blockNumber) => { - const blockHash = await this.blockchainService.getBlockHash(blockNumber); - const block = await this.blockchainService.getBlock(blockHash); - const txInfo = block.block.extrinsics.find((extrinsic) => extrinsic.hash.toString() === txHash.toString()); - this.logger.debug(`Extrinsics: ${block.block.extrinsics[0]}`); - - if (txInfo !== undefined) { - this.logger.verbose(`Found tx ${txHash} in block ${blockNumber}`); - const at = await this.blockchainService.api.at(blockHash.toHex()); - const events = await at.query.system.events(); - events.subscribe((records) => { - records.forEach(async (record) => { - const { event } = record; - const eventName = event.section; - const { method } = event; - const { data } = event; - this.logger.debug(`Received event: ${eventName} ${method} ${data}`); - if (eventName.search('capacity') !== -1 && method.search('Withdrawn') !== -1) { - const capacityWithDrawn = BigInt(data[1].toString()); - this.logger.debug(`Capacity withdrawn: ${capacityWithDrawn}`); - this.setEpochCapacity(epoch, capacityWithDrawn); - } - }); - }); - return blockHash; + private async handleMessagesFailure(jobId: string, moduleError: RegistryError): Promise<{ pause: boolean; retry: boolean }> { + try { + switch (moduleError.method) { + case 'TooManyMessagesInBlock': + // Re-try the job in the publish queue + return { pause: false, retry: true }; + case 'UnAuthorizedDelegate': + // Re-try the job in the publish, could be a signing error + return { pause: false, retry: true }; + case 'InvalidMessageSourceAccount': + case 'InvalidSchemaId': + case 'ExceedsMaxMessagePayloadSizeBytes': + case 'InvalidPayloadLocation': + case 'UnsupportedCid': + case 'InvalidCid': + return { pause: false, retry: false }; + default: + this.logger.error(`Unknown module error ${moduleError}`); + break; } - return undefined; - }); + } catch (error) { + this.logger.error(`Error handling module error: ${error}`); + } - const results = await Promise.all(txReceiptPromises); - const result = results.find((blockHash) => blockHash !== undefined); - return result; + // unknown error, pause the queue + return { pause: false, retry: false }; } - private async setEpochCapacity(epoch: string, capacityWithdrew: bigint) { + private async setEpochCapacity(epoch: string, capacityWithdrew: bigint): Promise { const epochCapacityKey = `epochCapacity:${epoch}`; try { - const epochCapacity = BigInt((await this.cacheManager.get(epochCapacityKey)) ?? 0); + const savedCapacity = await this.cacheManager.get(epochCapacityKey); + const epochCapacity = BigInt(savedCapacity ?? 0); const newEpochCapacity = epochCapacity + capacityWithdrew; const epochDurationBlocks = await this.blockchainService.getCurrentEpochLength(); const epochDuration = epochDurationBlocks * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; - await this.cacheManager.setex(epochCapacityKey, epochDuration, newEpochCapacity.toString()); } catch (error) { this.logger.error(`Error setting epoch capacity: ${error}`); - - throw error; } } } diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts index c4637583..7c205250 100644 --- a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts +++ b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts @@ -80,7 +80,7 @@ export class PublishingService extends BaseConsumer implements OnApplicationBoot }; // add a delay of 1 block to allow the tx reciept to go through before checking const delay = 1 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; - await this.txReceiptQueue.add(`Tx Receipt Job - ${job.id}`, job, { jobId: job.id, removeOnFail: false, removeOnComplete: 1000, delay }); + await this.txReceiptQueue.add(job.id, job, { jobId: job.id, removeOnFail: false, removeOnComplete: 1000, delay }); } private async checkCapacity(): Promise { diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index ce163677..6820d160 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -1,15 +1,14 @@ /* eslint-disable no-underscore-dangle */ import { Injectable, Logger, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common'; import { ApiPromise, ApiRx, HttpProvider, WsProvider } from '@polkadot/api'; -import { firstValueFrom } from 'rxjs'; +import { firstValueFrom, from } from 'rxjs'; import { options } from '@frequency-chain/api-augment'; import { KeyringPair } from '@polkadot/keyring/types'; -import { BlockHash, BlockNumber, SignedBlock } from '@polkadot/types/interfaces'; +import { BlockHash, BlockNumber, DispatchError, DispatchInfo, Hash, SignedBlock } from '@polkadot/types/interfaces'; import { SubmittableExtrinsic } from '@polkadot/api/types'; -import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; -import { u32, Option } from '@polkadot/types'; +import { AnyNumber, ISubmittableResult, RegistryError } from '@polkadot/types/types'; +import { u32, Option, u128 } from '@polkadot/types'; import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo, PalletSchemasSchema } from '@polkadot/types/lookup'; -import { Hash } from 'crypto'; import { ConfigService } from '../config/config.service'; import { Extrinsic } from './extrinsic'; @@ -157,4 +156,71 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS const schema: PalletSchemasSchema = await this.query('schemas', 'schemas', schemaId); return schema; } + + public async crawlBlockListForTx( + txHash: Hash, + blockList: bigint[], + successEvents: [{ pallet: string; event: string }], + ): Promise<{ success: boolean; blockHash?: BlockHash; capacityWithDrawn?: string; error?: RegistryError }> { + const txReceiptPromises: Promise<{ success: boolean; blockHash?: BlockHash; capacityWithDrawn?: string; error?: RegistryError }>[] = blockList.map(async (blockNumber) => { + const blockHash = await this.getBlockHash(blockNumber); + const block = await this.getBlock(blockHash); + const txInfo = block.block.extrinsics.find((extrinsic) => extrinsic.hash.toString() === txHash.toString()); + + if (!txInfo) { + return { success: false }; + } + + this.logger.verbose(`Found tx ${txHash} in block ${blockNumber}`); + const at = await this.api.at(blockHash.toHex()); + const eventsPromise = firstValueFrom(at.query.system.events()); + + let isTxSuccess = false; + let totalBlockCapacity: bigint = 0n; + let txError: RegistryError | undefined; + + try { + const events = await eventsPromise; + + events.forEach((record) => { + const { event } = record; + const eventName = event.section; + const { method } = event; + const { data } = event; + this.logger.debug(`Received event: ${eventName} ${method} ${data}`); + + // find capacity withdrawn event + if (eventName.search('capacity') !== -1 && method.search('Withdrawn') !== -1) { + // allow lowercase constructor for eslint + // eslint-disable-next-line new-cap + const currentCapacity: u128 = new u128(this.api.registry, data[1]); + totalBlockCapacity += currentCapacity.toBigInt(); + } + + // check custom success events + if (successEvents.find((successEvent) => successEvent.pallet === eventName && successEvent.event === method)) { + this.logger.debug(`Found success event ${eventName} ${method}`); + isTxSuccess = true; + } + + // check for system extrinsic failure + if (eventName.search('system') !== -1 && method.search('ExtrinsicFailed') !== -1) { + const dispatchError = data[0] as DispatchError; + const moduleThatErrored = dispatchError.asModule; + const moduleError = dispatchError.registry.findMetaError(moduleThatErrored); + txError = moduleError; + this.logger.error(`Extrinsic failed with error: ${JSON.stringify(moduleError)}`); + } + }); + } catch (error) { + this.logger.error(error); + } + this.logger.debug(`Total capacity withdrawn in block: ${totalBlockCapacity.toString()}`); + return { success: isTxSuccess, blockHash, capacityWithDrawn: totalBlockCapacity.toString(), error: txError }; + }); + const results = await Promise.all(txReceiptPromises); + const result = results.find((receipt) => receipt.blockHash !== undefined); + this.logger.debug(`Found tx receipt: ${JSON.stringify(result)}`); + return result ?? { success: false }; + } } From eb5af358c506fafa2d2c21749e6c18f0e64100ae Mon Sep 17 00:00:00 2001 From: Aramik Date: Tue, 3 Oct 2023 12:33:25 -0700 Subject: [PATCH 032/137] handle nonces atomically (#73) --- .../batch.announcer.service.ts | 2 +- .../batching.processor.service.ts | 2 +- .../worker/src/publisher/ipfs.publisher.ts | 5 ++- .../worker/src/publisher/nonce.service.ts | 41 +++++++++++++++++++ .../worker/src/publisher/publisher.module.ts | 3 +- .../src/blockchain/blockchain.service.ts | 4 ++ .../libs/common/src/utils/redis.ts | 1 + .../content-watcher/lua/incrementNonce.lua | 21 ++++++++++ 8 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 services/content-watcher/apps/worker/src/publisher/nonce.service.ts create mode 100644 services/content-watcher/lua/incrementNonce.lua diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts index 8d73c83b..8260116f 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts @@ -43,7 +43,7 @@ export class BatchAnnouncementService extends BaseConsumer implements OnModuleDe try { const publisherJob = await this.ipfsPublisher.announce(job.data); // eslint-disable-next-line no-promise-executor-return - await this.publishQueue.add(publisherJob.id, publisherJob); + await this.publishQueue.add(publisherJob.id, publisherJob, { jobId: publisherJob.id, removeOnComplete: 1000 }); this.logger.log(`Completed job ${job.id} of type ${job.name}`); return job.data; } catch (e) { diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts index 07c05bd2..dc06c918 100644 --- a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts +++ b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts @@ -124,7 +124,7 @@ export class BatchingProcessorService { schemaId: DsnpSchemas.getSchemaId(this.configService.environment, QueueConstants.QUEUE_NAME_TO_ANNOUNCEMENT_MAP.get(queueName)!), announcements, } as IBatchAnnouncerJobData; - await this.outputQueue.add(`Batch Job - ${metaData.batchId}`, job, { jobId: metaData.batchId, removeOnFail: false, removeOnComplete: 100 }); + await this.outputQueue.add(`Batch Job - ${metaData.batchId}`, job, { jobId: metaData.batchId, removeOnFail: false, removeOnComplete: 1000 }); } try { const result = await this.redis.multi().del(lockedBatchMetaDataKey).del(lockedBatchDataKey).exec(); diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts index 79a68e91..10150b56 100644 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts @@ -7,6 +7,7 @@ import { BlockchainService } from '../../../../libs/common/src/blockchain/blockc import { ConfigService } from '../../../../libs/common/src/config/config.service'; import { IPublisherJob } from '../interfaces/publisher-job.interface'; import { createKeys } from '../../../../libs/common/src/blockchain/create-keys'; +import { NonceService } from './nonce.service'; @Injectable() export class IPFSPublisher { @@ -15,6 +16,7 @@ export class IPFSPublisher { constructor( private configService: ConfigService, private blockchainService: BlockchainService, + private nonceService: NonceService, ) { this.logger = new Logger(IPFSPublisher.name); } @@ -35,7 +37,8 @@ export class IPFSPublisher { providerKeys, tx, ); - const [txHash] = await ext.signAndSend(); + const nonce = await this.nonceService.getNextNonce(); + const [txHash] = await ext.signAndSend(nonce); if (!txHash) { throw new Error('Tx hash is undefined'); } diff --git a/services/content-watcher/apps/worker/src/publisher/nonce.service.ts b/services/content-watcher/apps/worker/src/publisher/nonce.service.ts new file mode 100644 index 00000000..a92b8506 --- /dev/null +++ b/services/content-watcher/apps/worker/src/publisher/nonce.service.ts @@ -0,0 +1,41 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import Redis from 'ioredis'; +import fs from 'fs'; +import { createKeys } from '../../../../libs/common/src/blockchain/create-keys'; +import { RedisUtils } from '../../../../libs/common/src/utils/redis'; +import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; +import { ConfigService } from '../../../../libs/common/src/config/config.service'; + +@Injectable() +export class NonceService implements OnApplicationBootstrap { + private logger: Logger; + + private accountId: Uint8Array; + + constructor( + @InjectRedis() private redis: Redis, + private blockchainService: BlockchainService, + private configService: ConfigService, + ) { + this.logger = new Logger(NonceService.name); + redis.defineCommand('incrementNonce', { + numberOfKeys: 1, + lua: fs.readFileSync('lua/incrementNonce.lua', 'utf8'), + }); + } + + async onApplicationBootstrap() { + this.accountId = createKeys(this.configService.getProviderAccountSeedPhrase()).publicKey; + const nextNonce = await this.getNextNonce(); + this.logger.log(`nonce is set to ${nextNonce}`); + } + + async getNextNonce(): Promise { + const nonce = await this.blockchainService.getNonce(this.accountId); + // @ts-ignore + const nextNonce = await this.redis.incrementNonce(RedisUtils.CHAIN_NONCE_KEY, nonce); + this.logger.debug(`nextNonce ${nextNonce}`); + return nextNonce; + } +} diff --git a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts index 4e6cf3e8..6042d389 100644 --- a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts +++ b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts @@ -12,6 +12,7 @@ import { ConfigService } from '../../../../libs/common/src/config/config.service import { BlockchainModule } from '../../../../libs/common/src/blockchain/blockchain.module'; import { IPFSPublisher } from './ipfs.publisher'; import { QueueConstants } from '../../../../libs/common/src'; +import { NonceService } from './nonce.service'; @Module({ imports: [ @@ -77,7 +78,7 @@ import { QueueConstants } from '../../../../libs/common/src'; ), ], controllers: [], - providers: [PublishingService, IPFSPublisher], + providers: [PublishingService, IPFSPublisher, NonceService], exports: [BullModule, PublishingService, IPFSPublisher], }) export class PublisherModule {} diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 6820d160..22a88ced 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -157,6 +157,10 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS return schema; } + public async getNonce(account: Uint8Array): Promise { + return this.rpc('system', 'accountNextIndex', account); + } + public async crawlBlockListForTx( txHash: Hash, blockList: bigint[], diff --git a/services/content-watcher/libs/common/src/utils/redis.ts b/services/content-watcher/libs/common/src/utils/redis.ts index 6e02db33..3eee81a2 100644 --- a/services/content-watcher/libs/common/src/utils/redis.ts +++ b/services/content-watcher/libs/common/src/utils/redis.ts @@ -7,6 +7,7 @@ export namespace RedisUtils { * batch Lock expire time which applies during closing operation */ export const BATCH_LOCK_EXPIRE_SECONDS = 6; + export const CHAIN_NONCE_KEY = 'chain:nonce'; const ASSET_DATA_KEY_PREFIX = 'asset:data'; const ASSET_METADATA_KEY_PREFIX = 'asset:metadata'; const BATCH_DATA_KEY_PREFIX = 'batch:data'; diff --git a/services/content-watcher/lua/incrementNonce.lua b/services/content-watcher/lua/incrementNonce.lua new file mode 100644 index 00000000..dd5e6d8e --- /dev/null +++ b/services/content-watcher/lua/incrementNonce.lua @@ -0,0 +1,21 @@ +--[[ +Input: +KEYS[1] nonce key +ARGV[1] current nonce +Output: +N OK (current nonce) +]] +local nonceKey = KEYS[1] +local currentNonce = tonumber(ARGV[1]) +local rcall = redis.call + +local nonce = rcall("GET",nonceKey) +if not nonce or tonumber(nonce) < currentNonce then + rcall('SET', nonceKey, currentNonce) +else + rcall('INCR', nonceKey) +end +return rcall("GET", nonceKey) + + + From ab42130fe62ea1b9d77980a7478b10b6c3f2b257 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:34:05 -0500 Subject: [PATCH 033/137] Fix a bug in tx receipt (#77) * fix a bug left in tx receipt * lint * check for succes --- .../apps/worker/src/monitor/tx.status.monitor.service.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts index f5158d09..b5a20439 100644 --- a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts +++ b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts @@ -41,13 +41,18 @@ export class TxStatusMonitoringService extends BaseConsumer { } const txResult = await this.blockchainService.crawlBlockListForTx(job.data.txHash, blockList, [{ pallet: 'messages', event: 'MessageStored' }]); + this.setEpochCapacity(txCapacityEpoch, BigInt(txResult.capacityWithDrawn ?? 0n)); + + if (txResult.success && txResult.blockHash && !txResult.error) { + this.logger.verbose(`Successfully found ${job.data.txHash} found in block ${txResult.blockHash}`); + return txResult; + } + // if tx has not yet included in a block, throw error to retry till max attempts if (!txResult.blockHash && !txResult.error) { throw new Error(`Tx not found in block list, retrying (attempts=${job.attemptsMade})`); } - this.setEpochCapacity(txCapacityEpoch, BigInt(txResult.capacityWithDrawn ?? 0n)); - if (txResult.error && job.attemptsMade <= (job.opts.attempts ?? 3)) { this.logger.debug(`Error found in tx result: ${JSON.stringify(txResult.error)}`); const errorReport = await this.handleMessagesFailure(job.data.id, txResult.error); From 426df4a15c5e4e9b3bc24561c702c802d1615225 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:49:46 -0500 Subject: [PATCH 034/137] `MessageStored` -> `MessagesStored` (#78) --- .../apps/worker/src/monitor/tx.status.monitor.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts index b5a20439..9d1207d5 100644 --- a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts +++ b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts @@ -39,7 +39,7 @@ export class TxStatusMonitoringService extends BaseConsumer { for (let i = previousKnownBlockNumber; i <= currentFinalizedBlockNumber && i < previousKnownBlockNumber + numberBlocksToParse; i += 1n) { blockList.push(i); } - const txResult = await this.blockchainService.crawlBlockListForTx(job.data.txHash, blockList, [{ pallet: 'messages', event: 'MessageStored' }]); + const txResult = await this.blockchainService.crawlBlockListForTx(job.data.txHash, blockList, [{ pallet: 'messages', event: 'MessagesStored' }]); this.setEpochCapacity(txCapacityEpoch, BigInt(txResult.capacityWithDrawn ?? 0n)); From 5af8f6c43a26c61f9a0238c874707c052989e0a2 Mon Sep 17 00:00:00 2001 From: Aramik Date: Wed, 4 Oct 2023 13:18:30 -0700 Subject: [PATCH 035/137] fixed nonce and transaction receipt (#80) --- .../batch.announcer.service.ts | 2 +- .../src/monitor/status.monitor.module.ts | 12 -- .../src/monitor/tx.status.monitor.service.ts | 63 +++++---- .../worker/src/publisher/nonce.service.ts | 20 ++- .../src/publisher/publishing.service.ts | 9 +- .../src/blockchain/blockchain.service.ts | 120 +++++++++--------- .../libs/common/src/utils/redis.ts | 17 ++- .../content-watcher/lua/incrementNonce.lua | 29 +++-- 8 files changed, 153 insertions(+), 119 deletions(-) diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts index 8260116f..e861dd9c 100644 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts +++ b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts @@ -43,7 +43,7 @@ export class BatchAnnouncementService extends BaseConsumer implements OnModuleDe try { const publisherJob = await this.ipfsPublisher.announce(job.data); // eslint-disable-next-line no-promise-executor-return - await this.publishQueue.add(publisherJob.id, publisherJob, { jobId: publisherJob.id, removeOnComplete: 1000 }); + await this.publishQueue.add(publisherJob.id, publisherJob, { jobId: publisherJob.id, removeOnComplete: 1000, attempts: 3 }); this.logger.log(`Completed job ${job.id} of type ${job.name}`); return job.data; } catch (e) { diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts index c8690822..f1d50f88 100644 --- a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts +++ b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts @@ -54,24 +54,12 @@ import { QueueConstants } from '../../../../libs/common/src'; { name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, removeOnComplete: true, removeOnFail: false, }, }, { name: QueueConstants.PUBLISH_QUEUE_NAME, - defaultJobOptions: { - attempts: 1, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, }, ), ], diff --git a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts index 9d1207d5..11a7cea6 100644 --- a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts +++ b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts @@ -1,7 +1,7 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Processor, InjectQueue } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; -import { Job, Queue } from 'bullmq'; +import { Job, Queue, UnrecoverableError } from 'bullmq'; import Redis from 'ioredis'; import { MILLISECONDS_PER_SECOND } from 'time-constants'; import { RegistryError } from '@polkadot/types/types'; @@ -11,6 +11,7 @@ import { QueueConstants } from '../../../../libs/common/src'; import { SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; import { BlockchainConstants } from '../../../../libs/common/src/blockchain/blockchain-constants'; import { BaseConsumer } from '../BaseConsumer'; +import { IPublisherJob } from '../interfaces/publisher-job.interface'; @Injectable() @Processor(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, { @@ -34,42 +35,44 @@ export class TxStatusMonitoringService extends BaseConsumer { const previousKnownBlockNumber = (await this.blockchainService.getBlock(job.data.lastFinalizedBlockHash)).block.header.number.toBigInt(); const currentFinalizedBlockNumber = await this.blockchainService.getLatestFinalizedBlockNumber(); const blockList: bigint[] = []; - const blockDelay = 1 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; for (let i = previousKnownBlockNumber; i <= currentFinalizedBlockNumber && i < previousKnownBlockNumber + numberBlocksToParse; i += 1n) { blockList.push(i); } - const txResult = await this.blockchainService.crawlBlockListForTx(job.data.txHash, blockList, [{ pallet: 'messages', event: 'MessagesStored' }]); + const txResult = await this.blockchainService.crawlBlockListForTx(job.data.txHash, blockList, [{ pallet: 'system', event: 'ExtrinsicSuccess' }]); - this.setEpochCapacity(txCapacityEpoch, BigInt(txResult.capacityWithDrawn ?? 0n)); - - if (txResult.success && txResult.blockHash && !txResult.error) { - this.logger.verbose(`Successfully found ${job.data.txHash} found in block ${txResult.blockHash}`); - return txResult; - } - - // if tx has not yet included in a block, throw error to retry till max attempts - if (!txResult.blockHash && !txResult.error) { - throw new Error(`Tx not found in block list, retrying (attempts=${job.attemptsMade})`); - } + if (!txResult.found) { + if (job.attemptsMade < (job.opts.attempts ?? 3)) { + // if tx has not yet included in a block, throw error to retry till max attempts + throw new Error(`Tx not found in block list, retrying (attempts=${job.attemptsMade})`); + } else { + this.logger.warn(`Could not fetch the transaction adding to publish again! ${job.id}`); + // could not find the transaction, this might happen if transaction never gets into a block + await this.retryPublishJob(job.data.referencePublishJob); + } + } else { + // found the tx + await this.setEpochCapacity(txCapacityEpoch, BigInt(txResult.capacityWithDrawn ?? 0n)); + if (txResult.error) { + this.logger.debug(`Error found in tx result: ${JSON.stringify(txResult.error)}`); + const errorReport = await this.handleMessagesFailure(job.data.id, txResult.error); - if (txResult.error && job.attemptsMade <= (job.opts.attempts ?? 3)) { - this.logger.debug(`Error found in tx result: ${JSON.stringify(txResult.error)}`); - const errorReport = await this.handleMessagesFailure(job.data.id, txResult.error); - const failedError = new Error(`Job ${job.data.id} failed with error ${JSON.stringify(txResult.error)}`); + if (errorReport.pause) { + await this.publishQueue.pause(); + } - if (errorReport.pause) { - await this.publishQueue.pause(); + if (errorReport.retry) { + await this.retryPublishJob(job.data.referencePublishJob); + } else { + throw new UnrecoverableError(`Job ${job.data.id} failed with error ${JSON.stringify(txResult.error)}`); + } } - if (errorReport.retry) { - this.logger.debug(`Retrying job ${job.data.id}`); - await this.publishQueue.removeRepeatableByKey(job.data.referencePublishJob.id); - await this.publishQueue.add(job.data.referencePublishJob.id, job.data.referencePublishJob, { delay: blockDelay }); + if (txResult.success) { + this.logger.verbose(`Successfully found ${job.data.txHash} found in block ${txResult.blockHash}`); } } - await this.txReceiptQueue.removeRepeatableByKey(job.data.id); - throw new Error(`Job ${job.data.id} failed with error ${JSON.stringify(txResult.error)}`); + return txResult; } catch (e) { this.logger.error(e); throw e; @@ -85,8 +88,6 @@ export class TxStatusMonitoringService extends BaseConsumer { // Re-try the job in the publish queue return { pause: false, retry: true }; case 'UnAuthorizedDelegate': - // Re-try the job in the publish, could be a signing error - return { pause: false, retry: true }; case 'InvalidMessageSourceAccount': case 'InvalidSchemaId': case 'ExceedsMaxMessagePayloadSizeBytes': @@ -121,4 +122,10 @@ export class TxStatusMonitoringService extends BaseConsumer { this.logger.error(`Error setting epoch capacity: ${error}`); } } + + private async retryPublishJob(publishJob: IPublisherJob) { + this.logger.debug(`Retrying job ${publishJob.id}`); + await this.publishQueue.remove(publishJob.id); + await this.publishQueue.add(`Retrying publish job - ${publishJob.id}`, publishJob, { jobId: publishJob.id }); + } } diff --git a/services/content-watcher/apps/worker/src/publisher/nonce.service.ts b/services/content-watcher/apps/worker/src/publisher/nonce.service.ts index a92b8506..798a1a35 100644 --- a/services/content-watcher/apps/worker/src/publisher/nonce.service.ts +++ b/services/content-watcher/apps/worker/src/publisher/nonce.service.ts @@ -20,7 +20,7 @@ export class NonceService implements OnApplicationBootstrap { ) { this.logger = new Logger(NonceService.name); redis.defineCommand('incrementNonce', { - numberOfKeys: 1, + numberOfKeys: RedisUtils.NUMBER_OF_NONCE_KEYS_TO_CHECK, lua: fs.readFileSync('lua/incrementNonce.lua', 'utf8'), }); } @@ -33,9 +33,25 @@ export class NonceService implements OnApplicationBootstrap { async getNextNonce(): Promise { const nonce = await this.blockchainService.getNonce(this.accountId); + const keys = this.getNextPossibleKeys(nonce); // @ts-ignore - const nextNonce = await this.redis.incrementNonce(RedisUtils.CHAIN_NONCE_KEY, nonce); + const nextNonceIndex = await this.redis.incrementNonce(...keys, keys.length, RedisUtils.NONCE_KEY_EXPIRE_SECONDS); + if (nextNonceIndex === -1) { + this.logger.warn(`nextNonce was full even with ${RedisUtils.NUMBER_OF_NONCE_KEYS_TO_CHECK} ${nonce}`); + return Number(nonce) + RedisUtils.NUMBER_OF_NONCE_KEYS_TO_CHECK; + } + const nextNonce = Number(nonce) + nextNonceIndex - 1; this.logger.debug(`nextNonce ${nextNonce}`); return nextNonce; } + + // eslint-disable-next-line class-methods-use-this + getNextPossibleKeys(currentNonce: number): string[] { + const keys: string[] = []; + for (let i = 0; i < RedisUtils.NUMBER_OF_NONCE_KEYS_TO_CHECK; i += 1) { + const key = currentNonce + i; + keys.push(RedisUtils.getNonceKey(`${key}`)); + } + return keys; + } } diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts index 7c205250..5347d7a0 100644 --- a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts +++ b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts @@ -25,7 +25,7 @@ export class PublishingService extends BaseConsumer implements OnApplicationBoot constructor( @InjectRedis() private cacheManager: Redis, - @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue, + @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue: Queue, @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, private blockchainService: BlockchainService, private configService: ConfigService, @@ -78,9 +78,10 @@ export class PublishingService extends BaseConsumer implements OnApplicationBoot txHash, referencePublishJob: jobData, }; - // add a delay of 1 block to allow the tx reciept to go through before checking - const delay = 1 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; - await this.txReceiptQueue.add(job.id, job, { jobId: job.id, removeOnFail: false, removeOnComplete: 1000, delay }); + // add a delay of 1 block to allow the tx receipt to go through before checking + const initialDelay = 1 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; + const retryDelay = 3 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; + await this.txReceiptQueue.add(`Receipt Job - ${job.id}`, job, { jobId: job.id, delay: initialDelay, attempts: 4, backoff: { type: 'exponential', delay: retryDelay } }); } private async checkCapacity(): Promise { diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 22a88ced..5042be89 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -165,66 +165,68 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS txHash: Hash, blockList: bigint[], successEvents: [{ pallet: string; event: string }], - ): Promise<{ success: boolean; blockHash?: BlockHash; capacityWithDrawn?: string; error?: RegistryError }> { - const txReceiptPromises: Promise<{ success: boolean; blockHash?: BlockHash; capacityWithDrawn?: string; error?: RegistryError }>[] = blockList.map(async (blockNumber) => { - const blockHash = await this.getBlockHash(blockNumber); - const block = await this.getBlock(blockHash); - const txInfo = block.block.extrinsics.find((extrinsic) => extrinsic.hash.toString() === txHash.toString()); - - if (!txInfo) { - return { success: false }; - } - - this.logger.verbose(`Found tx ${txHash} in block ${blockNumber}`); - const at = await this.api.at(blockHash.toHex()); - const eventsPromise = firstValueFrom(at.query.system.events()); - - let isTxSuccess = false; - let totalBlockCapacity: bigint = 0n; - let txError: RegistryError | undefined; - - try { - const events = await eventsPromise; - - events.forEach((record) => { - const { event } = record; - const eventName = event.section; - const { method } = event; - const { data } = event; - this.logger.debug(`Received event: ${eventName} ${method} ${data}`); - - // find capacity withdrawn event - if (eventName.search('capacity') !== -1 && method.search('Withdrawn') !== -1) { - // allow lowercase constructor for eslint - // eslint-disable-next-line new-cap - const currentCapacity: u128 = new u128(this.api.registry, data[1]); - totalBlockCapacity += currentCapacity.toBigInt(); - } - - // check custom success events - if (successEvents.find((successEvent) => successEvent.pallet === eventName && successEvent.event === method)) { - this.logger.debug(`Found success event ${eventName} ${method}`); - isTxSuccess = true; - } - - // check for system extrinsic failure - if (eventName.search('system') !== -1 && method.search('ExtrinsicFailed') !== -1) { - const dispatchError = data[0] as DispatchError; - const moduleThatErrored = dispatchError.asModule; - const moduleError = dispatchError.registry.findMetaError(moduleThatErrored); - txError = moduleError; - this.logger.error(`Extrinsic failed with error: ${JSON.stringify(moduleError)}`); - } - }); - } catch (error) { - this.logger.error(error); - } - this.logger.debug(`Total capacity withdrawn in block: ${totalBlockCapacity.toString()}`); - return { success: isTxSuccess, blockHash, capacityWithDrawn: totalBlockCapacity.toString(), error: txError }; - }); + ): Promise<{ found: boolean; success: boolean; blockHash?: BlockHash; capacityWithDrawn?: string; error?: RegistryError }> { + const txReceiptPromises: Promise<{ found: boolean; success: boolean; blockHash?: BlockHash; capacityWithDrawn?: string; error?: RegistryError }>[] = blockList.map( + async (blockNumber) => { + const blockHash = await this.getBlockHash(blockNumber); + const block = await this.getBlock(blockHash); + const txInfo = block.block.extrinsics.find((extrinsic) => extrinsic.hash.toString() === txHash.toString()); + + if (!txInfo) { + return { found: false, success: false }; + } + + this.logger.verbose(`Found tx ${txHash} in block ${blockNumber}`); + const at = await this.api.at(blockHash.toHex()); + const eventsPromise = firstValueFrom(at.query.system.events()); + + let isTxSuccess = false; + let totalBlockCapacity: bigint = 0n; + let txError: RegistryError | undefined; + + try { + const events = await eventsPromise; + + events.forEach((record) => { + const { event } = record; + const eventName = event.section; + const { method } = event; + const { data } = event; + this.logger.debug(`Received event: ${eventName} ${method} ${data}`); + + // find capacity withdrawn event + if (eventName.search('capacity') !== -1 && method.search('Withdrawn') !== -1) { + // allow lowercase constructor for eslint + // eslint-disable-next-line new-cap + const currentCapacity: u128 = new u128(this.api.registry, data[1]); + totalBlockCapacity += currentCapacity.toBigInt(); + } + + // check custom success events + if (successEvents.find((successEvent) => successEvent.pallet === eventName && successEvent.event === method)) { + this.logger.debug(`Found success event ${eventName} ${method}`); + isTxSuccess = true; + } + + // check for system extrinsic failure + if (eventName.search('system') !== -1 && method.search('ExtrinsicFailed') !== -1) { + const dispatchError = data[0] as DispatchError; + const moduleThatErrored = dispatchError.asModule; + const moduleError = dispatchError.registry.findMetaError(moduleThatErrored); + txError = moduleError; + this.logger.error(`Extrinsic failed with error: ${JSON.stringify(moduleError)}`); + } + }); + } catch (error) { + this.logger.error(error); + } + this.logger.debug(`Total capacity withdrawn in block: ${totalBlockCapacity.toString()}`); + return { found: true, success: isTxSuccess, blockHash, capacityWithDrawn: totalBlockCapacity.toString(), error: txError }; + }, + ); const results = await Promise.all(txReceiptPromises); - const result = results.find((receipt) => receipt.blockHash !== undefined); + const result = results.find((receipt) => receipt.found); this.logger.debug(`Found tx receipt: ${JSON.stringify(result)}`); - return result ?? { success: false }; + return result ?? { found: false, success: false }; } } diff --git a/services/content-watcher/libs/common/src/utils/redis.ts b/services/content-watcher/libs/common/src/utils/redis.ts index 3eee81a2..55ca5dd1 100644 --- a/services/content-watcher/libs/common/src/utils/redis.ts +++ b/services/content-watcher/libs/common/src/utils/redis.ts @@ -7,7 +7,18 @@ export namespace RedisUtils { * batch Lock expire time which applies during closing operation */ export const BATCH_LOCK_EXPIRE_SECONDS = 6; - export const CHAIN_NONCE_KEY = 'chain:nonce'; + /** + * To be able to provide mostly unique nonces to submit transactions on chain we would need to check a number of + * temporarily locked keys on redis side and get the first available one. This number defines the number of keys + * we should look into before giving up + */ + export const NUMBER_OF_NONCE_KEYS_TO_CHECK = 50; + /** + * Nonce keys have to get expired shortly so that if any of nonce numbers get skipped we would still have a way to + * submit them after expiration + */ + export const NONCE_KEY_EXPIRE_SECONDS = 2; + const CHAIN_NONCE_KEY = 'chain:nonce'; const ASSET_DATA_KEY_PREFIX = 'asset:data'; const ASSET_METADATA_KEY_PREFIX = 'asset:metadata'; const BATCH_DATA_KEY_PREFIX = 'batch:data'; @@ -33,4 +44,8 @@ export namespace RedisUtils { export function getLockKey(suffix: string) { return `${LOCK_KEY_PREFIX}:${suffix}`; } + + export function getNonceKey(suffix: string) { + return `${CHAIN_NONCE_KEY}:${suffix}`; + } } diff --git a/services/content-watcher/lua/incrementNonce.lua b/services/content-watcher/lua/incrementNonce.lua index dd5e6d8e..f7f6ec98 100644 --- a/services/content-watcher/lua/incrementNonce.lua +++ b/services/content-watcher/lua/incrementNonce.lua @@ -1,21 +1,26 @@ --[[ Input: -KEYS[1] nonce key -ARGV[1] current nonce +KEYS[N] nonce keys +ARGV[1] number of keys +ARGV[2] key expire time in seconds Output: -N OK (current nonce) +-1 ERROR (none of keys worked) +N OK (chosen key index) ]] -local nonceKey = KEYS[1] -local currentNonce = tonumber(ARGV[1]) +local keysSize = tonumber(ARGV[1]) +local expireInSeconds = tonumber(ARGV[2]) local rcall = redis.call -local nonce = rcall("GET",nonceKey) -if not nonce or tonumber(nonce) < currentNonce then - rcall('SET', nonceKey, currentNonce) -else - rcall('INCR', nonceKey) -end -return rcall("GET", nonceKey) +local i = 1 +repeat + local nextKey = KEYS[i] + if rcall("EXISTS", nextKey) == 0 then + rcall('SETEX', nextKey, expireInSeconds , 1) + return i + end + i = i + 1 +until( i > keysSize) +return -1 From be5b87f48cf77e65e118d839e64f99fe423a0e81 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Fri, 6 Oct 2023 15:44:23 -0500 Subject: [PATCH 036/137] Docker compose with frq,redis,ipfs (#81) * docker compose with frq,redis,ipfs * add docker env * finalize * add simple procedure to readme * fix * fix env file for docker compose * env file for docker, update docker files * cleanup * no need for chain names, we are chain agnostic * cleanup * add missing port * worker dont need to expose ports * everyone gets a volume --- services/content-watcher/.dockerignore | 7 + services/content-watcher/.env.docker.dev | 30 +++++ .../.github/workflows/release.yml | 33 +---- services/content-watcher/.gitignore | 2 +- services/content-watcher/Dockerfile | 36 +++-- services/content-watcher/README.md | 40 +++++- .../apps/api/src/api.module.ts | 123 ++++++++++-------- services/content-watcher/dev.Dockerfile | 10 +- .../content-watcher/docker-compose.dev.yaml | 77 ++++++++--- .../libs/common/src/config/env.config.ts | 3 +- services/content-watcher/package-lock.json | 3 +- services/content-watcher/package.json | 1 - 12 files changed, 240 insertions(+), 125 deletions(-) create mode 100644 services/content-watcher/.dockerignore create mode 100644 services/content-watcher/.env.docker.dev diff --git a/services/content-watcher/.dockerignore b/services/content-watcher/.dockerignore new file mode 100644 index 00000000..b1f29d38 --- /dev/null +++ b/services/content-watcher/.dockerignore @@ -0,0 +1,7 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +dist +.env* +env.template diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.docker.dev new file mode 100644 index 00000000..e3d64590 --- /dev/null +++ b/services/content-watcher/.env.docker.dev @@ -0,0 +1,30 @@ +# Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development +# IPFS_ENDPOINT="https://ipfs.infura.io:5001" +# IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" +# IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" +# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" +IPFS_ENDPOINT="http://kubo_ipfs:5001" +IPFS_BASIC_AUTH_USER="" +IPFS_BASIC_AUTH_SECRET="" +IPFS_GATEWAY_URL="http://kubo_ipfs:8080/ipfs/[CID]" + +FREQUENCY_URL=ws://frequency:9944 +PROVIDER_ID=1 +REDIS_URL=redis://redis:6379 +BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 +QUEUE_HIGH_WATER=1000 +PROVIDER_ACCOUNT_SEED_PHRASE="//Ferdie" +WEBHOOK_FAILURE_THRESHOLD=3 +HEALTH_CHECK_SUCCESS_THRESHOLD=10 +WEBHOOK_RETRY_INTERVAL_SECONDS=10 +HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 +HEALTH_CHECK_MAX_RETRIES=4 +CAPACITY_LIMIT='{"type":"percentage", "value":80}' +ENVIRONMENT="dev" +API_PORT=3000 + +FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 +ASSET_EXPIRATION_INTERVAL_SECONDS=300 +BATCH_INTERVAL_SECONDS=12 +BATCH_MAX_COUNT=1000 +ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 diff --git a/services/content-watcher/.github/workflows/release.yml b/services/content-watcher/.github/workflows/release.yml index 3f2f7c90..f0289d88 100644 --- a/services/content-watcher/.github/workflows/release.yml +++ b/services/content-watcher/.github/workflows/release.yml @@ -20,7 +20,7 @@ env: NEW_RELEASE_TAG_FROM_UI: ${{github.event.inputs.release-version}} TEST_RUN: ${{startsWith(github.event.inputs.release-version || github.ref_name, 'v0.0.1')}} DOCKER_HUB_PROFILE: amplicalabs - IMAGE_NAME: content-publishing-service + IMAGE_NAME: content-publishing-service-api jobs: build-and-publish-container-image: @@ -45,24 +45,13 @@ jobs: uses: actions/checkout@v3 with: ref: ${{env.NEW_RELEASE_TAG_FROM_UI}} - - name: Set up tags for standalone image - id: standalone-tags + - name: Set up tags for cp image + id: cp-tags uses: docker/metadata-action@v4 with: flavor: | latest=auto - prefix=standalone-,onlatest=true - images: | - ${{env.DOCKER_HUB_PROFILE}}/${{env.IMAGE_NAME}} - tags: | - type=semver,pattern={{version}} - - name: Set up tags for app-only image - id: app-only-tags - uses: docker/metadata-action@v4 - with: - flavor: | - latest=auto - prefix=apponly-,onlatest=true + prefix=api-,onlatest=true images: | ${{env.DOCKER_HUB_PROFILE}}/${{env.IMAGE_NAME}} tags: | @@ -79,21 +68,11 @@ jobs: with: username: ${{secrets.DOCKERHUB_USERNAME_FC}} password: ${{secrets.DOCKERHUB_TOKEN_FC}} - - name: Build and Push Standalone (Complete) Container Image - uses: docker/build-push-action@v4 - with: - context: . - platforms: linux/amd64 - push: ${{env.TEST_RUN != 'true'}} - file: ./Dockerfile - target: standalone - tags: ${{ steps.standalone-tags.outputs.tags }} - - name: Build and Push App-Only Container Image + - name: Build and Push Content-Publishing-Service Image uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64 push: ${{env.TEST_RUN != 'true'}} file: ./Dockerfile - target: app-only - tags: ${{ steps.app-only-tags.outputs.tags }} + tags: ${{ steps.cp-tags.outputs.tags }} \ No newline at end of file diff --git a/services/content-watcher/.gitignore b/services/content-watcher/.gitignore index a93fd541..b859bff7 100644 --- a/services/content-watcher/.gitignore +++ b/services/content-watcher/.gitignore @@ -1,6 +1,6 @@ node_modules dist -.env* +.env .vscode coverage .idea diff --git a/services/content-watcher/Dockerfile b/services/content-watcher/Dockerfile index 3135bdae..cdf91f45 100644 --- a/services/content-watcher/Dockerfile +++ b/services/content-watcher/Dockerfile @@ -1,32 +1,28 @@ -FROM --platform=linux/amd64 node:18 as build +# Use a multi-stage build for efficiency +FROM node:18 AS builder + +WORKDIR /usr/src/app -# TODO: The deployment docker image should install the content publishing -# service from NPM rather than building from source -WORKDIR /app COPY package*.json ./ + RUN npm install -# Build / Copy the rest of the application files to the container and build COPY . . -RUN npm run build - -FROM build as app-only -EXPOSE 3000 +# Build the application +RUN npm run build -ENTRYPOINT npm start +# Production stage +FROM node:18 -FROM build as standalone +WORKDIR /usr/src/app -# Install Redis on top of the base image -RUN apt-get -y update -RUN apt-get -y install redis -RUN sed -e 's/^appendonly .*$/appendonly yes/' /etc/redis/redis.conf > /etc/redis/redis.conf.appendonly -RUN mv /etc/redis/redis.conf.appendonly /etc/redis/redis.conf +COPY --from=builder /usr/src/app/dist ./dist +COPY package*.json ./ -ENV REDIS_URL=redis://localhost:6379 +RUN npm install --only=production +EXPOSE 3000 +ENV START_PROCESS="api" -VOLUME [ "/var/lib/redis" ] -# Start the application -ENTRYPOINT service redis-server start && npm start +CMD ["sh", "-c", "if [ \"$START_PROCESS\" = \"api\" ]; then npm run start:api:prod; else npm run start:worker:prod; fi"] diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index cfcb7194..0d411fd3 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -1,3 +1,41 @@ # Content Publisher -A microservice to publish DSNP content to frequency. +Content Publisher is a microservice designed to publish DSNP (Decentralized Social Networking Protocol) content to the Frequency blockchain. This README provides step-by-step instructions to set up and run the service. + +## Table of Contents + +- [Content Publisher](#content-publisher) + - [Table of Contents](#table-of-contents) + - [Prerequisites](#prerequisites) + - [Getting Started](#getting-started) + - [Clone the Repository](#clone-the-repository) + +## Prerequisites + +Before you begin, ensure you have met the following requirements: + +- **Docker:** Content Publisher is designed to run in a Docker environment. Make sure Docker is installed on your system. + +## Getting Started + +Follow these steps to set up and run Content Publisher: + +### Clone the Repository + +1. Clone the Content Publisher repository to your local machine: + + ```bash + git clone https://github.com/amplicalabls/content-publishing-service.git + ``` + +2. Modify any environment variables in the `.env` file as needed. For docker compose env `.env.docker.dev` file is used. + +3. Run the following command to start the service: + + ```bash + docker-compose -f docker-compose.dev.yaml up + ``` + +4. Visit [Swagger UI](http://localhost:3000/api/docs/swagger) to view the API documentation and submit requests to the service. + +5. Visit [Bullboard](http://localhost:3000/queues) to view the job queue and the status of the jobs. diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index af9ed54a..955812e2 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -16,52 +16,84 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; @Module({ imports: [ - BullModule.forRoot({ - connection: { - enableOfflineQueue: false, + ConfigModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], }), + BullModule.registerQueue( + { + name: QueueConstants.ASSET_QUEUE_NAME, + }, + { + name: QueueConstants.REQUEST_QUEUE_NAME, + }, + { + name: QueueConstants.BROADCAST_QUEUE_NAME, + }, + { + name: QueueConstants.REPLY_QUEUE_NAME, + }, + { + name: QueueConstants.REACTION_QUEUE_NAME, + }, + { + name: QueueConstants.TOMBSTONE_QUEUE_NAME, + }, + { + name: QueueConstants.UPDATE_QUEUE_NAME, + }, + { + name: QueueConstants.PROFILE_QUEUE_NAME, + }, + { + name: QueueConstants.BATCH_QUEUE_NAME, + }, + { + name: QueueConstants.PUBLISH_QUEUE_NAME, + }, + { + name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, + }, + { + name: QueueConstants.STATUS_QUEUE_NAME, + }, + ), + + // Bullboard UI BullBoardModule.forRoot({ route: '/queues', adapter: ExpressAdapter, }), - BullModule.registerQueue({ - name: QueueConstants.ASSET_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.REQUEST_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.BROADCAST_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.REPLY_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.REACTION_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.TOMBSTONE_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.UPDATE_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.PROFILE_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.BATCH_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.PUBLISH_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.STATUS_QUEUE_NAME, - }), - BullBoardModule.forFeature({ name: QueueConstants.ASSET_QUEUE_NAME, adapter: BullMQAdapter, @@ -110,17 +142,6 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; name: QueueConstants.STATUS_QUEUE_NAME, adapter: BullMQAdapter, }), - ConfigModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), EventEmitterModule.forRoot({ // Use this instance throughout the application global: true, diff --git a/services/content-watcher/dev.Dockerfile b/services/content-watcher/dev.Dockerfile index 75173082..99fcb102 100644 --- a/services/content-watcher/dev.Dockerfile +++ b/services/content-watcher/dev.Dockerfile @@ -1,6 +1,10 @@ -FROM node:18-alpine3.17 +FROM node:18 WORKDIR /app +COPY . . -# Start the application -CMD ["npm", "run", "start:dev:docker"] +RUN npm install +EXPOSE 3000 +ENV START_PROCESS="api" + +CMD ["sh", "-c", "if [ \"$START_PROCESS\" = \"api\" ]; then npm run start:api; else npm run start:worker; fi"] diff --git a/services/content-watcher/docker-compose.dev.yaml b/services/content-watcher/docker-compose.dev.yaml index f00d6080..59231f62 100644 --- a/services/content-watcher/docker-compose.dev.yaml +++ b/services/content-watcher/docker-compose.dev.yaml @@ -1,7 +1,36 @@ version: '3' services: - content-publishing-service: + redis: + image: redis:latest + ports: + - 6379:6379 + volumes: + - redis_data:/data/redis + networks: + - content-publishing-service + + frequency: + image: frequencychain/instant-seal-node:latest + ports: + - 9944:9944 + networks: + - content-publishing-service + volumes: + - frequency_data:/data/frequency + + kubo_ipfs: + image: ipfs/kubo:latest + ports: + - 4001:4001 + - 5001:5001 + - 8080:8080 + networks: + - content-publishing-service + volumes: + - ipfs_data:/data/ipfs + + content-publishing-service-api: build: context: . dockerfile: dev.Dockerfile @@ -9,30 +38,42 @@ services: - 3000:3000 env_file: - .env.docker.dev + environment: + - START_PROCESS=api + - REDIS_URL=redis://redis:6379 volumes: - - ./:/app + - ./:/app + depends_on: + - redis + - frequency + - kubo_ipfs networks: - - content-publishing-service-network-dev + - content-publishing-service - redis: - image: redis:latest - ports: - - 6379:6379 - networks: - - content-publishing-service-network-dev + content-publishing-service-worker: + build: + context: . + dockerfile: dev.Dockerfile + env_file: + - .env.docker.dev + environment: + - START_PROCESS=worker + - REDIS_URL=redis://redis:6379 volumes: - - redis_data:/data - - frequency: - image: frequencychain/instant-seal-node:latest - ports: - - 9944:9944 + - ./:/app + depends_on: + - redis + - frequency + - kubo_ipfs networks: - - content-publishing-service-network-dev - container_name: frequency-node + - content-publishing-service volumes: redis_data: + ipfs_data: + frequency_data: networks: - content-publishing-service-network-dev: + content-publishing-service: + + diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 4e868f6a..df363de2 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -1,6 +1,5 @@ -import Joi, { number } from 'joi'; +import Joi from 'joi'; import { ConfigModuleOptions } from '@nestjs/config'; -import { mnemonicValidate } from '@polkadot/util-crypto'; import { EnvironmentDto } from '..'; export const configModuleOptions: ConfigModuleOptions = { diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index e199e128..c2159312 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -15332,8 +15332,9 @@ }, "node_modules/ts-node-dev": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", "dev": true, - "license": "MIT", "dependencies": { "chokidar": "^3.5.1", "dynamic-dedupe": "^0.3.0", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 1ef5304d..6ffbdc0b 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -14,7 +14,6 @@ "start:worker:dev": "set -a ; . .env ; nest start worker", "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", "start:worker:debug": "set -a ; . .env ;nest start worker --debug --watch", - "start:dev:docker": "npm ci && ts-node-dev -r tsconfig-paths/register apps/api/src/main.ts", "docker-build": "docker build -t content-publishing-service .", "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", "docker-run": "docker build -t content-publishing-service-deploy . ; docker run -p 6379:6379 --env-file .env content-publishing-service-deploy", From 8ace7de0e52debd4c82523a8b419c5bae9ec8521 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:19:03 +0000 Subject: [PATCH 037/137] Bump webpack from 5.74.0 to 5.88.2 Bumps [webpack](https://github.com/webpack/webpack) from 5.74.0 to 5.88.2. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.74.0...v5.88.2) --- updated-dependencies: - dependency-name: webpack dependency-type: indirect ... Signed-off-by: dependabot[bot] --- services/content-watcher/package-lock.json | 9 --------- 1 file changed, 9 deletions(-) diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index c2159312..4336ebf3 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -10972,15 +10972,6 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, - "node_modules/redoc-cli/node_modules/@types/mkdirp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", - "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", - "extraneous": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/redoc-cli/node_modules/@types/node": { "version": "15.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", From d93163c4271cca3a7b3b8d694c2c4f5d270eb238 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 10 Oct 2023 20:37:02 -0500 Subject: [PATCH 038/137] park some cleanups --- .../.github/workflows/release.yml | 4 +- services/content-watcher/INSTALLING.md | 8 +- services/content-watcher/README.md | 2 +- .../content-watcher/docker-compose.dev.yaml | 17 +- .../libs/common/src/config/swagger_config.ts | 4 +- services/content-watcher/lua/addToBatch.lua | 37 -- .../content-watcher/lua/incrementNonce.lua | 26 -- services/content-watcher/lua/lockBatch.lua | 53 --- services/content-watcher/package-lock.json | 4 +- services/content-watcher/package.json | 14 +- services/content-watcher/swagger.yaml | 383 +----------------- 11 files changed, 29 insertions(+), 523 deletions(-) delete mode 100644 services/content-watcher/lua/addToBatch.lua delete mode 100644 services/content-watcher/lua/incrementNonce.lua delete mode 100644 services/content-watcher/lua/lockBatch.lua diff --git a/services/content-watcher/.github/workflows/release.yml b/services/content-watcher/.github/workflows/release.yml index f0289d88..665af5d6 100644 --- a/services/content-watcher/.github/workflows/release.yml +++ b/services/content-watcher/.github/workflows/release.yml @@ -20,7 +20,7 @@ env: NEW_RELEASE_TAG_FROM_UI: ${{github.event.inputs.release-version}} TEST_RUN: ${{startsWith(github.event.inputs.release-version || github.ref_name, 'v0.0.1')}} DOCKER_HUB_PROFILE: amplicalabs - IMAGE_NAME: content-publishing-service-api + IMAGE_NAME: content-watcher-service jobs: build-and-publish-container-image: @@ -68,7 +68,7 @@ jobs: with: username: ${{secrets.DOCKERHUB_USERNAME_FC}} password: ${{secrets.DOCKERHUB_TOKEN_FC}} - - name: Build and Push Content-Publishing-Service Image + - name: Build and Push content-watcher-service Image uses: docker/build-push-action@v4 with: context: . diff --git a/services/content-watcher/INSTALLING.md b/services/content-watcher/INSTALLING.md index 4eaa5041..eacdd0a3 100644 --- a/services/content-watcher/INSTALLING.md +++ b/services/content-watcher/INSTALLING.md @@ -9,9 +9,9 @@ The application requires a Redis server that is configured with `Append-only fil ### Standalone (complete) image -The standalone container image is meant to be a complete solution for a provider. It contains a single instance of the main application, plus a pre-configured Redis server. Simply download the latest [container image](https://hub.docker.com/r/amplicalabs/content-publishing-service/) and deploy using your favorite container management system. +The standalone container image is meant to be a complete solution for a provider. It contains a single instance of the main application, plus a pre-configured Redis server. Simply download the latest [container image](https://hub.docker.com/r/amplicalabs/content-watcher-service/) and deploy using your favorite container management system. ``` - docker pull amplicalabs/content-publishing-service:standalone-latest + docker pull amplicalabs/content-watcher-service:standalone-latest ``` The internal Redis server included in the complete image is already configured for persistence; it is simply necessary to configure your container pod to map the directory `/var/lib/redis` to a persistent storage volume. @@ -22,9 +22,9 @@ Follow the instructions below for [configuration](#configuration), with the exce ### App-only image -The app-only image is meant to be used for providers who would rather utilize a Redis instance in their own (or their cloud infrastructure provider's) external Redis instance or service. To download the latest [container image](https://hub.docker.com/r/amplicalabs/content-publishing-service/), simply: +The app-only image is meant to be used for providers who would rather utilize a Redis instance in their own (or their cloud infrastructure provider's) external Redis instance or service. To download the latest [container image](https://hub.docker.com/r/amplicalabs/content-watcher-service/), simply: ``` - docker pull amplicalabs/content-publishing-service:apponly-latest + docker pull amplicalabs/content-watcher-service:apponly-latest ``` In this case, you need to ensure that the following settings are configured in your Redis instance: ``` diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index 0d411fd3..94f38b05 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -25,7 +25,7 @@ Follow these steps to set up and run Content Publisher: 1. Clone the Content Publisher repository to your local machine: ```bash - git clone https://github.com/amplicalabls/content-publishing-service.git + git clone https://github.com/amplicalabls/content-watcher-service.git ``` 2. Modify any environment variables in the `.env` file as needed. For docker compose env `.env.docker.dev` file is used. diff --git a/services/content-watcher/docker-compose.dev.yaml b/services/content-watcher/docker-compose.dev.yaml index 59231f62..6f69eb3e 100644 --- a/services/content-watcher/docker-compose.dev.yaml +++ b/services/content-watcher/docker-compose.dev.yaml @@ -8,14 +8,14 @@ services: volumes: - redis_data:/data/redis networks: - - content-publishing-service + - content-watcher-service frequency: image: frequencychain/instant-seal-node:latest ports: - 9944:9944 networks: - - content-publishing-service + - content-watcher-service volumes: - frequency_data:/data/frequency @@ -26,11 +26,11 @@ services: - 5001:5001 - 8080:8080 networks: - - content-publishing-service + - content-watcher-service volumes: - ipfs_data:/data/ipfs - content-publishing-service-api: + content-watcher-service-api: build: context: . dockerfile: dev.Dockerfile @@ -48,9 +48,9 @@ services: - frequency - kubo_ipfs networks: - - content-publishing-service + - content-watcher-service - content-publishing-service-worker: + content-watcher-service-worker: build: context: . dockerfile: dev.Dockerfile @@ -66,14 +66,15 @@ services: - frequency - kubo_ipfs networks: - - content-publishing-service + - content-watcher-service volumes: redis_data: ipfs_data: frequency_data: + networks: - content-publishing-service: + content-watcher-service: diff --git a/services/content-watcher/libs/common/src/config/swagger_config.ts b/services/content-watcher/libs/common/src/config/swagger_config.ts index 6d2ae21e..ca5648e5 100644 --- a/services/content-watcher/libs/common/src/config/swagger_config.ts +++ b/services/content-watcher/libs/common/src/config/swagger_config.ts @@ -4,8 +4,8 @@ import metadata from '../../../../apps/api/src/metadata'; export const initSwagger = async (app: INestApplication, apiPath: string) => { const options = new DocumentBuilder() - .setTitle('Content Publishing Service API') - .setDescription('Content Publishing Service API') + .setTitle('Content Watcher Service API') + .setDescription('Content Watcher Service API') .setVersion('1.0') .addBearerAuth({ type: 'http', diff --git a/services/content-watcher/lua/addToBatch.lua b/services/content-watcher/lua/addToBatch.lua deleted file mode 100644 index 60c220a0..00000000 --- a/services/content-watcher/lua/addToBatch.lua +++ /dev/null @@ -1,37 +0,0 @@ ---[[ -Input: -KEYS[1] batch metadata key -KEYS[2] batch data key -ARGV[1] metadata -ARGV[2] Job id -ARGV[3] data -Output: --1 ERROR -1 OK (new batch) -N OK (number of existing rows) -]] -local batchMetadataKey = KEYS[1] -local batchDataKey = KEYS[2] -local newMetadata = ARGV[1] -local newJobId = ARGV[2] -local newData = ARGV[3] -local rcall = redis.call -local rawMetadata = rcall("GET",batchMetadataKey) -local currentCount = 1 -if rawMetadata then - local metadata = cjson.decode(rawMetadata) - local rowCount = metadata['rowCount'] - if not rowCount then - return -1 - end - currentCount = rowCount + 1 - metadata['rowCount'] = currentCount - rcall("SET", batchMetadataKey, cjson.encode(metadata)) -else - rcall("SET", batchMetadataKey, newMetadata) -end -rcall("HSETNX", batchDataKey, newJobId, newData) -return currentCount - - - diff --git a/services/content-watcher/lua/incrementNonce.lua b/services/content-watcher/lua/incrementNonce.lua deleted file mode 100644 index f7f6ec98..00000000 --- a/services/content-watcher/lua/incrementNonce.lua +++ /dev/null @@ -1,26 +0,0 @@ ---[[ -Input: -KEYS[N] nonce keys -ARGV[1] number of keys -ARGV[2] key expire time in seconds -Output: --1 ERROR (none of keys worked) -N OK (chosen key index) -]] -local keysSize = tonumber(ARGV[1]) -local expireInSeconds = tonumber(ARGV[2]) -local rcall = redis.call - -local i = 1 -repeat - local nextKey = KEYS[i] - if rcall("EXISTS", nextKey) == 0 then - rcall('SETEX', nextKey, expireInSeconds , 1) - return i - end - i = i + 1 -until( i > keysSize) -return -1 - - - diff --git a/services/content-watcher/lua/lockBatch.lua b/services/content-watcher/lua/lockBatch.lua deleted file mode 100644 index 7b9b62bf..00000000 --- a/services/content-watcher/lua/lockBatch.lua +++ /dev/null @@ -1,53 +0,0 @@ ---[[ -Input: -KEYS[1] batch metadata key -KEYS[2] batch data key -KEYS[3] locked metadata key -KEYS[4] locked data key -ARGV[1] current timestamp -ARGV[2] timestamp interval -Output: --1 ERROR (lock existed for more than timeout) --2 ERROR (lock existed for less than timeout) -0 OK (there is no batch to close) -1 OK (locking the batch) -if the previous batch is still locked then return the data for that batch -if no locked previous batch then just return the data for current batch -]] -local batchMetadataKey = KEYS[1] -local batchDataKey = KEYS[2] -local lockedMetadataKey = KEYS[3] -local lockedDataKey = KEYS[4] -local currentTimestamp = tonumber(ARGV[1]) -local timestampInterval = tonumber(ARGV[2]) -local rcall = redis.call - -local metadata = rcall("GET",batchMetadataKey) -if not metadata then - return {0} -end - -local rawPreviousMetadata = rcall('GET', lockedMetadataKey) -if rawPreviousMetadata then - local previousMetadata = cjson.decode(rawPreviousMetadata) - local timePassed = currentTimestamp - tonumber(previousMetadata['lockTimestamp']) - if timePassed > timestampInterval then - return {-1, rcall('HGETALL', lockedDataKey), rawPreviousMetadata} - else - return {-2} - end -end - -rcall("RENAME",batchMetadataKey, lockedMetadataKey) -rcall("RENAME",batchDataKey, lockedDataKey) - -local rawPreviousMetadata2 = rcall('GET', lockedMetadataKey) -local previousMetadata2 = cjson.decode(rawPreviousMetadata2) -previousMetadata2['lockTimestamp']=currentTimestamp -local encodedMetadata = cjson.encode(previousMetadata2) -rcall('SET', lockedMetadataKey, encodedMetadata) - -return {1, rcall('HGETALL', lockedDataKey), encodedMetadata} - - - diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index c2159312..d3261474 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -1,11 +1,11 @@ { - "name": "content-publishing-service", + "name": "content-watcher-service", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "content-publishing-service", + "name": "content-watcher-service", "version": "0.1.0", "license": "Apache-2.0", "dependencies": { diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 6ffbdc0b..9c8024ab 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -1,5 +1,5 @@ { - "name": "content-publishing-service", + "name": "content-watcher-service", "version": "0.1.0", "description": "Services to publish content on DSNP/Frequency", "main": "dist/apps/api/main.js", @@ -14,10 +14,10 @@ "start:worker:dev": "set -a ; . .env ; nest start worker", "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", "start:worker:debug": "set -a ; . .env ;nest start worker --debug --watch", - "docker-build": "docker build -t content-publishing-service .", + "docker-build": "docker build -t content-watcher-service .", "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", - "docker-run": "docker build -t content-publishing-service-deploy . ; docker run -p 6379:6379 --env-file .env content-publishing-service-deploy", - "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs -f content-publishing-service", + "docker-run": "docker build -t content-watcher-service-deploy . ; docker run -p 6379:6379 --env-file .env content-watcher-service-deploy", + "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs -f content-watcher-service", "docker-stop:dev": "docker-compose -f docker-compose.dev.yaml stop", "clean": "rm -Rf dist", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", @@ -28,15 +28,15 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/AmplicaLabs/content-publishing-service.git" + "url": "git+https://github.com/AmplicaLabs/content-watcher-service.git" }, "keywords": [], "author": "", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/AmplicaLabs/content-publishing-service/issues" + "url": "https://github.com/AmplicaLabs/content-watcher-service/issues" }, - "homepage": "https://github.com/AmplicaLabs/content-publishing-service#readme", + "homepage": "https://github.com/AmplicaLabs/content-watcher-service#readme", "dependencies": { "@bull-board/api": "^5.8.3", "@bull-board/express": "^5.8.3", diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index 52ffaea0..6ba043b8 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -7,158 +7,9 @@ paths: responses: '200': description: '' - "/api/asset/upload": - put: - operationId: ApiController_assetUpload - parameters: [] - requestBody: - required: true - description: Asset files - content: - multipart/form-data: - schema: - "$ref": "#/components/schemas/FilesUploadDto" - responses: - '202': - description: '' - content: - application/json: - schema: - "$ref": "#/components/schemas/UploadResponseDto" - "/api/content/{userDsnpId}/broadcast": - post: - operationId: ApiController_broadcast - parameters: - - name: userDsnpId - required: true - in: path - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - "$ref": "#/components/schemas/BroadcastDto" - responses: - '202': - description: '' - content: - application/json: - schema: - "$ref": "#/components/schemas/AnnouncementResponseDto" - "/api/content/{userDsnpId}/reply": - post: - operationId: ApiController_reply - parameters: - - name: userDsnpId - required: true - in: path - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - "$ref": "#/components/schemas/ReplyDto" - responses: - '202': - description: '' - content: - application/json: - schema: - "$ref": "#/components/schemas/AnnouncementResponseDto" - "/api/content/{userDsnpId}/reaction": - post: - operationId: ApiController_reaction - parameters: - - name: userDsnpId - required: true - in: path - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - "$ref": "#/components/schemas/ReactionDto" - responses: - '202': - description: '' - content: - application/json: - schema: - "$ref": "#/components/schemas/AnnouncementResponseDto" - "/api/content/{userDsnpId}": - put: - operationId: ApiController_update - parameters: - - name: userDsnpId - required: true - in: path - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - "$ref": "#/components/schemas/UpdateDto" - responses: - '202': - description: '' - content: - application/json: - schema: - "$ref": "#/components/schemas/AnnouncementResponseDto" - delete: - operationId: ApiController_delete - parameters: - - name: userDsnpId - required: true - in: path - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - "$ref": "#/components/schemas/TombstoneDto" - responses: - '202': - description: '' - content: - application/json: - schema: - "$ref": "#/components/schemas/AnnouncementResponseDto" - "/api/profile/{userDsnpId}": - put: - operationId: ApiController_profile - parameters: - - name: userDsnpId - required: true - in: path - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - "$ref": "#/components/schemas/ProfileDto" - responses: - '202': - description: '' - content: - application/json: - schema: - "$ref": "#/components/schemas/AnnouncementResponseDto" info: - title: Content Publishing Service API - description: Content Publishing Service API + title: Content Watcher Service API + description: Content Watcher Service API version: '1.0' contact: {} tags: [] @@ -175,233 +26,3 @@ components: in: cookie name: SESSION schemas: - FilesUploadDto: - type: object - properties: - files: - type: array - items: - type: string - format: binary - required: - - files - UploadResponseDto: - type: object - properties: - assetIds: - type: array - items: - type: string - required: - - assetIds - AssetReferenceDto: - type: object - properties: - referenceId: - type: string - minLength: 1 - height: - type: number - minimum: 1 - width: - type: number - minimum: 1 - duration: - type: string - pattern: DURATION_REGEX - required: - - referenceId - AssetDto: - type: object - properties: - type: - type: string - enum: - - link - - image - - audio - - video - references: - type: array - items: - "$ref": "#/components/schemas/AssetReferenceDto" - name: - type: string - minLength: 1 - href: - type: string - minLength: 1 - required: - - type - TagDto: - type: object - properties: - type: - type: string - enum: - - mention - - hashtag - name: - type: string - minLength: 1 - mentionedId: - type: string - minLength: 1 - pattern: DSNP_USER_URI_REGEX - required: - - type - LocationDto: - type: object - properties: - name: - type: string - minLength: 1 - accuracy: - type: number - minimum: 0 - maximum: 100 - altitude: - type: number - latitude: - type: number - longitude: - type: number - radius: - type: number - minimum: 0 - units: - type: string - enum: - - cm - - m - - km - - inches - - feet - - miles - required: - - name - NoteActivityDto: - type: object - properties: - content: - type: string - minLength: 1 - published: - type: string - pattern: ISO8601_REGEX - assets: - type: array - items: - "$ref": "#/components/schemas/AssetDto" - name: - type: string - tag: - type: array - items: - "$ref": "#/components/schemas/TagDto" - location: - "$ref": "#/components/schemas/LocationDto" - required: - - content - - published - BroadcastDto: - type: object - properties: - content: - "$ref": "#/components/schemas/NoteActivityDto" - required: - - content - AnnouncementResponseDto: - type: object - properties: - referenceId: - type: string - required: - - referenceId - ReplyDto: - type: object - properties: - inReplyTo: - type: string - pattern: DSNP_CONTENT_URI_REGEX - content: - "$ref": "#/components/schemas/NoteActivityDto" - required: - - inReplyTo - - content - ReactionDto: - type: object - properties: - emoji: - type: string - minLength: 1 - pattern: DSNP_EMOJI_REGEX - apply: - type: number - minimum: 0 - maximum: 255 - inReplyTo: - type: string - pattern: DSNP_CONTENT_URI_REGEX - required: - - emoji - - apply - - inReplyTo - UpdateDto: - type: object - properties: - targetContentHash: - type: string - pattern: DSNP_CONTENT_HASH_REGEX - targetAnnouncementType: - type: string - enum: - - broadcast - - reply - content: - "$ref": "#/components/schemas/NoteActivityDto" - required: - - targetContentHash - - targetAnnouncementType - - content - TombstoneDto: - type: object - properties: - targetContentHash: - type: string - pattern: DSNP_CONTENT_HASH_REGEX - targetAnnouncementType: - type: string - enum: - - broadcast - - reply - required: - - targetContentHash - - targetAnnouncementType - ProfileActivityDto: - type: object - properties: - icon: - type: array - items: - "$ref": "#/components/schemas/AssetReferenceDto" - summary: - type: string - published: - type: string - pattern: ISO8601_REGEX - name: - type: string - tag: - type: array - items: - "$ref": "#/components/schemas/TagDto" - location: - "$ref": "#/components/schemas/LocationDto" - ProfileDto: - type: object - properties: - profile: - "$ref": "#/components/schemas/ProfileActivityDto" - required: - - profile From 020ce9a2e9192f80aee8aa63e3e41702a8d14ad8 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Wed, 11 Oct 2023 09:05:50 -0500 Subject: [PATCH 039/137] remove workers --- .../apps/api/test/app.e2e-spec.ts | 832 ------------------ .../apps/api/test/jest-e2e.json | 9 - .../apps/worker/src/BaseConsumer.ts | 58 -- .../asset_processor/asset.processor.module.ts | 57 -- .../asset.processor.service.ts | 49 -- .../batch_announcer/batch.announcer.module.ts | 84 -- .../batch.announcer.service.ts | 54 -- .../batch_announcer/batch.announcer.spec.ts | 92 -- .../src/batch_announcer/batch.announcer.ts | 90 -- .../batching.processor.module.ts | 82 -- .../batching.processor.service.ts | 163 ---- .../workers/broadcast.worker.ts | 30 - .../workers/profile.worker.ts | 30 - .../workers/reaction.worker.ts | 30 - .../workers/reply.worker.ts | 30 - .../workers/tombstone.worker.ts | 30 - .../workers/update.worker.ts | 30 - .../batch-announcer.job.interface.ts | 7 - .../src/interfaces/publisher-job.interface.ts | 10 - .../interfaces/status-monitor.interface.ts | 10 - .../content-watcher/apps/worker/src/main.ts | 8 - .../src/monitor/status.monitor.module.ts | 70 -- .../src/monitor/status.monitoring.spec.ts | 10 - .../src/monitor/tx.status.monitor.service.ts | 131 --- .../src/publisher/ipfs.publisher.spec.ts | 30 - .../worker/src/publisher/ipfs.publisher.ts | 52 -- .../worker/src/publisher/nonce.service.ts | 57 -- .../worker/src/publisher/publisher.module.ts | 84 -- .../src/publisher/publishing.service.ts | 152 ---- .../dsnp.announcement.processor.spec.ts | 210 ----- .../dsnp.announcement.processor.ts | 414 --------- .../request.processor.module.ts | 79 -- .../request.processor.service.ts | 57 -- .../apps/worker/src/worker.module.ts | 64 -- .../apps/worker/tsconfig.app.json | 9 - .../content-watcher/docker-compose.dev.yaml | 18 - services/content-watcher/package.json | 5 +- 37 files changed, 1 insertion(+), 3226 deletions(-) delete mode 100644 services/content-watcher/apps/api/test/app.e2e-spec.ts delete mode 100644 services/content-watcher/apps/api/test/jest-e2e.json delete mode 100644 services/content-watcher/apps/worker/src/BaseConsumer.ts delete mode 100644 services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts delete mode 100644 services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts delete mode 100644 services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts delete mode 100644 services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts delete mode 100644 services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts delete mode 100644 services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts delete mode 100644 services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts delete mode 100644 services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts delete mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts delete mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts delete mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts delete mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts delete mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts delete mode 100644 services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts delete mode 100644 services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts delete mode 100644 services/content-watcher/apps/worker/src/interfaces/publisher-job.interface.ts delete mode 100644 services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts delete mode 100644 services/content-watcher/apps/worker/src/main.ts delete mode 100644 services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts delete mode 100644 services/content-watcher/apps/worker/src/monitor/status.monitoring.spec.ts delete mode 100644 services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts delete mode 100644 services/content-watcher/apps/worker/src/publisher/ipfs.publisher.spec.ts delete mode 100644 services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts delete mode 100644 services/content-watcher/apps/worker/src/publisher/nonce.service.ts delete mode 100644 services/content-watcher/apps/worker/src/publisher/publisher.module.ts delete mode 100644 services/content-watcher/apps/worker/src/publisher/publishing.service.ts delete mode 100644 services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts delete mode 100644 services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts delete mode 100644 services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts delete mode 100644 services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts delete mode 100644 services/content-watcher/apps/worker/src/worker.module.ts delete mode 100644 services/content-watcher/apps/worker/tsconfig.app.json diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts deleted file mode 100644 index 19b1768a..00000000 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ /dev/null @@ -1,832 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable no-undef */ -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import request from 'supertest'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { randomFill } from 'crypto'; -import { ApiModule } from '../src/api.module'; - -describe('AppController E2E request verification!', () => { - let app: INestApplication; - let module: TestingModule; - // eslint-disable-next-line no-promise-executor-return - const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - const validLocation = { - name: 'name of location', - accuracy: 97, - altitude: 10, - latitude: 37.26, - longitude: -119.59, - radius: 10, - units: 'm', - }; - const validTags = [ - { - type: 'mention', - mentionedId: 'dsnp://78187493520', - }, - { - type: 'hashtag', - name: '#taggedUser', - }, - ]; - const validContentNoUploadedAssets = { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - name: 'name of note content', - assets: [ - { - type: 'link', - name: 'link asset', - href: 'http://example.com', - }, - ], - tag: validTags, - location: validLocation, - }; - const validBroadCastNoUploadedAssets = { - content: validContentNoUploadedAssets, - }; - const validReplyNoUploadedAssets = { - content: validContentNoUploadedAssets, - inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - }; - const validReaction = { - emoji: '🤌🏼', - apply: 5, - inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - }; - const validProfileNoUploadedAssets = { - summary: 'profile summary', - published: '1970-01-01T00:00:00+00:00', - name: 'name of profile content', - tag: validTags, - location: validLocation, - }; - - beforeEach(async () => { - module = await Test.createTestingModule({ - imports: [ApiModule], - }).compile(); - - app = module.createNestApplication(); - const eventEmitter = app.get(EventEmitter2); - eventEmitter.on('shutdown', async () => { - await app.close(); - }); - app.useGlobalPipes(new ValidationPipe()); - app.enableShutdownHooks(); - await app.init(); - }); - - it('(GET) /api/health', () => request(app.getHttpServer()).get('/api/health').expect(200).expect({ status: 200 })); - - describe('Validate Route params', () => { - it('invalid userDsnpId should fail', async () => { - const invalidDsnpUserId = '2gsjhdaj'; - return request(app.getHttpServer()) - .post(`/api/content/${invalidDsnpUserId}/broadcast`) - .send(validBroadCastNoUploadedAssets) - .expect(400) - .expect((res) => expect(res.text).toContain('must be a number string')); - }); - }); - - describe('(POST) /api/content/:dsnpUserId/broadcast', () => { - it('valid request without uploaded assets should work!', () => - request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(validBroadCastNoUploadedAssets) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - - it('valid broadcast request with uploaded assets should work!', async () => { - const file = Buffer.from('g'.repeat(30 * 1000 * 1000)); // 30MB - const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); - await sleep(1000); - const validBroadCastWithUploadedAssets = { - content: { - ...validContentNoUploadedAssets, - assets: [ - { - type: 'image', - name: 'image asset', - references: [ - { - referenceId: response.body.assetIds[0], - height: 123, - width: 321, - }, - ], - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(validBroadCastWithUploadedAssets) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId')); - }, 15000); - - it('request with not uploaded assets should fail!', async () => { - const badAssetCid = 'bafybeiap642764aat6txaap4qex4empkdtpjv7uabv47w1pdih3nflajpy'; - const validBroadCastWithUploadedAssets = { - content: { - ...validContentNoUploadedAssets, - assets: [ - { - type: 'image', - name: 'image asset', - references: [ - { - referenceId: badAssetCid, - }, - ], - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(validBroadCastWithUploadedAssets) - .expect(400) - .expect((res) => expect(res.text).toContain(`${badAssetCid} does not exist`)); - }); - - it('empty body should fail', () => { - const body = {}; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('should not be empty')); - }); - - it('empty content should fail', () => { - const body = { - content: { - content: '', - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('content must be longer than or equal to 1 characters')); - }); - - it('empty published should fail', () => { - const body = { - content: { - content: 'tests content', - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('published must match')); - }); - - it('invalid published should fail', () => { - const body2 = { - content: { - content: 'tests content', - published: '1980', - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body2) - .expect(400) - .expect((res) => expect(res.text).toContain('published must match')); - }); - - it('invalid assets type should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - assets: [ - { - type: 'invalid', - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('type must be one of the following values')); - }); - - it('image asset without references should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - assets: [ - { - type: 'image', - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('references should not be empty')); - }); - - it('image asset with non unique references id should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - assets: [ - { - type: 'image', - references: [ - { - referenceId: 'reference-id-1', - }, - { - referenceId: 'reference-id-1', - }, - ], - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('elements must be unique')); - }); - - it('link asset without href should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - assets: [ - { - type: 'link', - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('href must be longer than or equal to 1 character')); - }); - - it('link asset with invalid href protocol should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - assets: [ - { - type: 'link', - href: 'ftp://sgdjas8912yejc.com', - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('href must be a URL address')); - }); - - it('hashtag without name should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - tag: [ - { - type: 'hashtag', - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('name must be longer than or equal to 1 character')); - }); - - it('mentioned tag without mentionedId should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - tag: [ - { - type: 'mention', - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('mentionedId must be longer than or equal to 1 character')); - }); - - it('invalid tag type should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - tag: [ - { - type: 'invalid', - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('type must be one of the following values')); - }); - - it('location with invalid units should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - location: { - name: 'name of location', - units: 'invalid', - }, - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('units must be one of the following values')); - }); - - it('location with empty name should fail', () => { - const body = { - content: { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - location: { - name: '', - }, - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('name must be longer than or equal to 1 characters')); - }); - }); - - describe('(POST) /api/content/:dsnpUserId/reply', () => { - it('valid request without assets should work!', () => - request(app.getHttpServer()) - .post(`/api/content/123/reply`) - .send(validReplyNoUploadedAssets) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - - it('valid request with uploaded assets should work!', async () => { - const file = Buffer.from('h'.repeat(30 * 1000 * 1000)); // 30MB - const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); - await sleep(1000); - const validReplyWithUploadedAssets = { - ...validReplyNoUploadedAssets, - content: { - ...validContentNoUploadedAssets, - assets: [ - { - type: 'image', - name: 'image asset', - references: [ - { - referenceId: response.body.assetIds[0], - height: 123, - width: 321, - }, - ], - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/reply`) - .send(validReplyWithUploadedAssets) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId')); - }, 15000); - - it('request with not uploaded assets should fail!', async () => { - const badAssetCid = 'bafybeiap642764aat6txaap4qex4empkdtpjv7uabv47w1pdih3nflajpy'; - const validReplyWithUploadedAssets = { - ...validReplyNoUploadedAssets, - content: { - ...validContentNoUploadedAssets, - assets: [ - { - type: 'image', - name: 'image asset', - references: [ - { - referenceId: badAssetCid, - height: 123, - width: 321, - }, - ], - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/reply`) - .send(validReplyWithUploadedAssets) - .expect(400) - .expect((res) => expect(res.text).toContain(`${badAssetCid} does not exist`)); - }); - - it('empty body should fail', () => { - const body = {}; - return request(app.getHttpServer()) - .post(`/api/content/123/reply`) - .send(body) - .expect(400) - .expect((res) => expect(res.text).toContain('should not be empty')); - }); - - it('empty inReplyTo should fail', () => - request(app.getHttpServer()) - .post(`/api/content/123/reply`) - .send({ - content: validContentNoUploadedAssets, - }) - .expect(400) - .expect((res) => expect(res.text).toContain('inReplyTo must be a string'))); - - it('invalid inReplyTo should fail', () => - request(app.getHttpServer()) - .post(`/api/content/123/reply`) - .send({ - content: validContentNoUploadedAssets, - inReplyTo: 'shgdjas72gsjajasa', - }) - .expect(400) - .expect((res) => expect(res.text).toContain('inReplyTo must match'))); - }); - - describe('(POST) /api/content/:dsnpUserId/reaction', () => { - it('valid request should work!', () => - request(app.getHttpServer()) - .post(`/api/content/123/reaction`) - .send(validReaction) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - - it('valid emoji should fail', () => - request(app.getHttpServer()) - .post(`/api/content/123/reaction`) - .send({ - emoji: '2', - }) - .expect(400) - .expect((res) => expect(res.text).toContain('emoji must match'))); - - it('valid apply amount should fail', () => - request(app.getHttpServer()) - .post(`/api/content/123/reaction`) - .send({ - emoji: '😀', - apply: -1, - }) - .expect(400) - .expect((res) => expect(res.text).toContain('apply must not be less than 0'))); - - it('invalid inReplyTo should fail', () => - request(app.getHttpServer()) - .post(`/api/content/123/reaction`) - .send({ - emoji: '😀', - apply: 0, - inReplyTo: 'shgdjas72gsjajasa', - }) - .expect(400) - .expect((res) => expect(res.text).toContain('inReplyTo must match'))); - }); - - describe('(PUT) /api/content/:dsnpUserId', () => { - it('valid request without assets should work!', () => - request(app.getHttpServer()) - .put(`/api/content/123`) - .send({ - targetContentHash: '0x7653423447AF', - targetAnnouncementType: 'broadcast', - content: validContentNoUploadedAssets, - }) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - - it('valid request with uploaded assets should work!', async () => { - const file = Buffer.from('g'.repeat(30 * 1000 * 1000)); // 30MB - const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); - await sleep(1000); - const validContentWithUploadedAssets = { - ...validContentNoUploadedAssets, - assets: [ - { - type: 'image', - name: 'image asset', - references: [ - { - referenceId: response.body.assetIds[0], - height: 123, - width: 321, - }, - ], - }, - ], - }; - return request(app.getHttpServer()) - .put(`/api/content/123`) - .send({ - targetContentHash: '0x7653423447AF', - targetAnnouncementType: 'broadcast', - content: validContentWithUploadedAssets, - }) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId')); - }, 15000); - - it('request with not uploaded assets should fail!', async () => { - const badAssetCid = 'bafybeiap642764aat6txaap4qex4empkdtpjv7uabv47w1pdih3nflajpy'; - const validBroadCastWithUploadedAssets = { - ...validContentNoUploadedAssets, - assets: [ - { - type: 'image', - name: 'image asset', - references: [ - { - referenceId: badAssetCid, - }, - ], - }, - ], - }; - return request(app.getHttpServer()) - .put(`/api/content/123`) - .send({ - targetContentHash: '0x7653423447AF', - targetAnnouncementType: 'broadcast', - content: validBroadCastWithUploadedAssets, - }) - .expect(400) - .expect((res) => expect(res.text).toContain(`${badAssetCid} does not exist`)); - }); - - it('invalid targetAnnouncementType should fail', () => - request(app.getHttpServer()) - .put(`/api/content/123`) - .send({ - targetContentHash: '0x7653423447AF', - targetAnnouncementType: 'invalid', - content: validContentNoUploadedAssets, - }) - .expect(400) - .expect((res) => expect(res.text).toContain('targetAnnouncementType must be one of the following values'))); - - it('invalid targetContentHash should fail', () => - request(app.getHttpServer()) - .put(`/api/content/123`) - .send({ - targetContentHash: '6328462378', - targetAnnouncementType: 'reply', - content: validContentNoUploadedAssets, - }) - .expect(400) - .expect((res) => expect(res.text).toContain('targetContentHash must be in hexadecimal format!'))); - }); - - describe('(DELETE) /api/content/:dsnpUserId', () => { - it('valid request should work!', () => - request(app.getHttpServer()) - .delete(`/api/content/123`) - .send({ - targetContentHash: '0x7653423447AF', - targetAnnouncementType: 'reply', - }) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - - it('invalid targetAnnouncementType should fail', () => - request(app.getHttpServer()) - .delete(`/api/content/123`) - .send({ - targetContentHash: '0x7653423447AF', - targetAnnouncementType: 'invalid', - }) - .expect(400) - .expect((res) => expect(res.text).toContain('targetAnnouncementType must be one of the following values'))); - - it('invalid targetContentHash should fail', () => - request(app.getHttpServer()) - .delete(`/api/content/123`) - .send({ - targetContentHash: '6328462378', - targetAnnouncementType: 'reply', - }) - .expect(400) - .expect((res) => expect(res.text).toContain('targetContentHash must be in hexadecimal format!'))); - }); - - describe('(PUT) /api/profile/:userDsnpId', () => { - it('valid request without assets should work!', () => - request(app.getHttpServer()) - .put(`/api/profile/123`) - .send({ - profile: validProfileNoUploadedAssets, - }) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - - it('valid request with uploaded assets should work!', async () => { - const file = Buffer.from('n'.repeat(30 * 1000 * 1000)); // 30MB - const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file.jpg').expect(202); - await sleep(1000); - const validUploadWithUploadedAssets = { - ...validProfileNoUploadedAssets, - icon: [ - { - referenceId: response.body.assetIds[0], - height: 123, - width: 321, - }, - ], - }; - return request(app.getHttpServer()) - .put(`/api/profile/123`) - .send({ - profile: validUploadWithUploadedAssets, - }) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId')); - }, 15000); - - it('request with not uploaded icon should fail!', async () => { - const badAssetCid = 'bafybeiap642764aat6txaap4qex4empkdtpjv7uabv47w1pdih3nflajpy'; - const validUploadWithUploadedAssets = { - ...validProfileNoUploadedAssets, - icon: [ - { - referenceId: badAssetCid, - height: 123, - width: 321, - }, - ], - }; - return request(app.getHttpServer()) - .put(`/api/profile/123`) - .send({ - profile: validUploadWithUploadedAssets, - }) - .expect(400) - .expect((res) => expect(res.text).toContain(`${badAssetCid} does not exist`)); - }); - - it('request with non-image uploaded assets should fail!', async () => { - const file = Buffer.from('s'.repeat(30 * 1000 * 1000)); // 30MB - const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file.mp3').expect(202); - await sleep(1000); - const profileContent = { - ...validProfileNoUploadedAssets, - icon: [ - { - referenceId: response.body.assetIds[0], - height: 123, - width: 321, - }, - ], - }; - return request(app.getHttpServer()) - .put(`/api/profile/123`) - .send({ - profile: profileContent, - }) - .expect(400) - .expect((res) => expect(res.text).toContain('is not an image!')); - }, 15000); - - it('empty profile should fail', () => - request(app.getHttpServer()) - .put(`/api/profile/123`) - .send({}) - .expect(400) - .expect((res) => expect(res.text).toContain('should not be empty'))); - - it('invalid published should fail', () => - request(app.getHttpServer()) - .put(`/api/profile/123`) - .send({ - profile: { - published: '1980', - }, - }) - .expect(400) - .expect((res) => expect(res.text).toContain('published must match'))); - - it('non unique reference ids should fail', () => - request(app.getHttpServer()) - .put(`/api/profile/123`) - .send({ - profile: { - icon: [ - { - referenceId: 'reference-id-1', - }, - { - referenceId: 'reference-id-1', - }, - ], - }, - }) - .expect(400) - .expect((res) => expect(res.text).toContain('elements must be unique'))); - }); - - describe('(PUT) /api/asset/upload', () => { - it('valid request should work!', () => - request(app.getHttpServer()) - .put(`/api/asset/upload`) - .attach('files', Buffer.from(validContentNoUploadedAssets.toString()), 'image.jpg') - .expect(202) - .expect((res) => expect(res.text).toContain('assetIds'))); - - it('invalid mime should fail', () => - request(app.getHttpServer()) - .put(`/api/asset/upload`) - .attach('files', Buffer.from(validContentNoUploadedAssets.toString()), 'doc.txt') - .expect(422) - .expect((res) => expect(res.text).toContain('expected type is'))); - - it('valid request should work!', async () => { - const file1 = Buffer.from('a'.repeat(30 * 1000)); // 30KB - const file2 = Buffer.from('t'.repeat(30 * 1000 * 1000)); // 30MB - const file3 = Buffer.from('z'.repeat(100 * 1000 * 1000)); // 100MB - await request(app.getHttpServer()) - .put(`/api/asset/upload`) - .attach('files', file1, 'file1.jpg') - .attach('files', file2, 'file2.mp3') - .attach('files', file3, 'file3.mpeg') - .expect(202) - .expect((res) => expect(res.text).toContain('assetIds')); - }, 15000); - - it('upload asset should be uploaded to IPFS', async () => { - const buffer = new Uint32Array(100 * 1000); - randomFill(buffer, (err, buf) => { - if (err) throw err; - }); - const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', Buffer.from(buffer), 'file1.jpg').expect(202); - const assetId = response.body.assetIds[0]; - await sleep(2000); - return request(app.getHttpServer()) - .get(`/api/dev/asset/${assetId}`) - .expect(200) - .expect((res) => expect(Buffer.from(res.body)).toEqual(Buffer.from(buffer))); - }, 15000); - - it('not uploaded asset should return not found', async () => { - const assetId = 'bafybeieva67sj7hiiywi4kxsamcc2t2y2pptni2ki6gu63azj3pkznbzna'; - return request(app.getHttpServer()).get(`/api/dev/asset/${assetId}`).expect(404); - }); - }); - - afterEach(async () => { - try { - await app.close(); - } catch (err) { - console.error(err); - } - }, 15000); -}); diff --git a/services/content-watcher/apps/api/test/jest-e2e.json b/services/content-watcher/apps/api/test/jest-e2e.json deleted file mode 100644 index e9d912f3..00000000 --- a/services/content-watcher/apps/api/test/jest-e2e.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "moduleFileExtensions": ["js", "json", "ts"], - "rootDir": ".", - "testEnvironment": "node", - "testRegex": ".e2e-spec.ts$", - "transform": { - "^.+\\.(t|j)s$": "ts-jest" - } -} diff --git a/services/content-watcher/apps/worker/src/BaseConsumer.ts b/services/content-watcher/apps/worker/src/BaseConsumer.ts deleted file mode 100644 index 52315b16..00000000 --- a/services/content-watcher/apps/worker/src/BaseConsumer.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { OnWorkerEvent, WorkerHost } from '@nestjs/bullmq'; -import { Logger, OnModuleDestroy } from '@nestjs/common'; -import { Job, Worker } from 'bullmq'; -import { ProcessingUtils } from '../../../libs/common/src/utils/processing'; - -export abstract class BaseConsumer extends WorkerHost implements OnModuleDestroy { - protected logger: Logger; - - private actives: Set; - - protected constructor() { - super(); - this.logger = new Logger(this.constructor.name); - this.actives = new Set(); - } - - trackJob(jobId: string) { - this.actives.add(jobId); - } - - unTrackJob(jobId: string) { - this.actives.delete(jobId); - } - - async onModuleDestroy(): Promise { - await this.worker?.close(false); - let maxWaitMs = ProcessingUtils.MAX_WAIT_FOR_GRACE_FULL_SHUTDOWN_MS; - while (this.actives.size > 0 && maxWaitMs > 0) { - // eslint-disable-next-line no-await-in-loop - await ProcessingUtils.delay(ProcessingUtils.DELAY_TO_CHECK_FOR_SHUTDOWN_MS); - maxWaitMs -= ProcessingUtils.DELAY_TO_CHECK_FOR_SHUTDOWN_MS; - } - } - - /** - * any method overriding this method should call this method directly, so we are able to track the executing jobs - */ - @OnWorkerEvent('active') - onActive(job: Job) { - this.trackJob(job.id!); - } - - /** - * any method overriding this method should call this method directly, so we are able to track the executing jobs - */ - @OnWorkerEvent('completed') - onCompleted(job: Job) { - this.unTrackJob(job.id!); - } - - /** - * any method overriding this method should call this method directly, so we are able to track the executing jobs - */ - @OnWorkerEvent('failed') - onFailed(job: Job) { - this.unTrackJob(job.id!); - } -} diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts deleted file mode 100644 index 107c7bdf..00000000 --- a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.module.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* -https://docs.nestjs.com/modules -*/ - -import { BullModule } from '@nestjs/bullmq'; -import { Module } from '@nestjs/common'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { QueueConstants } from '../../../../libs/common/src'; -import { AssetProcessorService } from './asset.processor.service'; -import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; -import { ConfigModule } from '../../../../libs/common/src/config/config.module'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; - -@Module({ - imports: [ - ConfigModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue({ - name: QueueConstants.ASSET_QUEUE_NAME, - }), - ], - providers: [AssetProcessorService, IpfsService], - exports: [BullModule, AssetProcessorService, IpfsService], -}) -export class AssetProcessorModule {} diff --git a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts b/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts deleted file mode 100644 index 1f841456..00000000 --- a/services/content-watcher/apps/worker/src/asset_processor/asset.processor.service.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, Logger } from '@nestjs/common'; -import { Job } from 'bullmq'; -import Redis from 'ioredis'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { QueueConstants } from '../../../../libs/common/src'; -import { IAssetJob } from '../../../../libs/common/src/interfaces/asset-job.interface'; -import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; -import { BaseConsumer } from '../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.ASSET_QUEUE_NAME) -export class AssetProcessorService extends BaseConsumer { - constructor( - @InjectRedis() private redis: Redis, - private configService: ConfigService, - private ipfsService: IpfsService, - ) { - super(); - } - - async process(job: Job): Promise { - this.logger.log(`Processing job ${job.id} of type ${job.name}`); - this.logger.debug(job.asJSON()); - const redisResults = await this.redis.getBuffer(job.data.contentLocation); - if (!redisResults) { - throw new Error(`Content stored in ${job.data.contentLocation} does not exist!`); - } - const pinned = await this.ipfsService.ipfsPin(job.data.mimeType, redisResults, false); - this.logger.log(pinned); - if (job.data.ipfsCid !== pinned.cid) { - throw new Error(`Cid does not match ${job.data.ipfsCid} != ${pinned.cid}`); - } - } - - // eslint-disable-next-line class-methods-use-this - @OnWorkerEvent('completed') - async onCompleted(job: Job) { - this.logger.log(`completed ${job.id}`); - const secondsPassed = Math.round((Date.now() - job.timestamp) / 1000); - const expectedSecondsToExpire = this.configService.getAssetExpirationIntervalSeconds(); - const secondsToExpire = Math.max(0, expectedSecondsToExpire - secondsPassed); - const result = await this.redis.pipeline().expire(job.data.contentLocation, secondsToExpire, 'LT').expire(job.data.metadataLocation, secondsToExpire, 'LT').exec(); - this.logger.debug(result); - // calling in the end for graceful shutdowns - super.onCompleted(job); - } -} diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts deleted file mode 100644 index 101c92b8..00000000 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.module.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* -https://docs.nestjs.com/modules -*/ - -import { BullModule } from '@nestjs/bullmq'; -import { Module } from '@nestjs/common'; -import { EventEmitterModule } from '@nestjs/event-emitter'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { BatchAnnouncementService } from './batch.announcer.service'; -import { ConfigModule } from '../../../../libs/common/src/config/config.module'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { BatchAnnouncer } from './batch.announcer'; -import { QueueConstants } from '../../../../libs/common/src'; -import { BlockchainModule } from '../../../../libs/common/src/blockchain/blockchain.module'; -import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; - -@Module({ - imports: [ - ConfigModule, - BlockchainModule, - EventEmitterModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue( - { - name: QueueConstants.PUBLISH_QUEUE_NAME, - defaultJobOptions: { - attempts: 1, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.BATCH_QUEUE_NAME, - defaultJobOptions: { - attempts: 1, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - ), - ], - controllers: [], - providers: [BatchAnnouncementService, BatchAnnouncer, IpfsService], - exports: [BullModule, BatchAnnouncementService, BatchAnnouncer, IpfsService], -}) -export class BatchAnnouncerModule {} diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts deleted file mode 100644 index e861dd9c..00000000 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.service.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; -import { Job, Queue } from 'bullmq'; -import Redis from 'ioredis'; -import { SchedulerRegistry } from '@nestjs/schedule'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { BatchAnnouncer } from './batch.announcer'; -import { CAPACITY_EPOCH_TIMEOUT_NAME } from '../../../../libs/common/src/constants'; -import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; -import { QueueConstants } from '../../../../libs/common/src'; -import { BaseConsumer } from '../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.BATCH_QUEUE_NAME, { - concurrency: 2, -}) -export class BatchAnnouncementService extends BaseConsumer implements OnModuleDestroy { - constructor( - @InjectRedis() private cacheManager: Redis, - @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, - private configService: ConfigService, - private ipfsPublisher: BatchAnnouncer, - private schedulerRegistry: SchedulerRegistry, - private eventEmitter: EventEmitter2, - ) { - super(); - } - - async onModuleDestroy(): Promise { - try { - this.schedulerRegistry.deleteTimeout(CAPACITY_EPOCH_TIMEOUT_NAME); - } catch (e) { - // 💀 // - } - // calling in the end for graceful shutdowns - await super.onModuleDestroy(); - } - - async process(job: Job): Promise { - this.logger.log(`Processing job ${job.id} of type ${job.name}`); - try { - const publisherJob = await this.ipfsPublisher.announce(job.data); - // eslint-disable-next-line no-promise-executor-return - await this.publishQueue.add(publisherJob.id, publisherJob, { jobId: publisherJob.id, removeOnComplete: 1000, attempts: 3 }); - this.logger.log(`Completed job ${job.id} of type ${job.name}`); - return job.data; - } catch (e) { - this.logger.error(`Error processing job ${job.id} of type ${job.name}: ${e}`); - throw e; - } - } -} diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts deleted file mode 100644 index b2127b75..00000000 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { expect, describe, jest, it, beforeEach } from '@jest/globals'; -import assert from 'assert'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { FrequencyParquetSchema } from '@dsnp/frequency-schemas/types/frequency'; -// eslint-disable-next-line import/no-extraneous-dependencies -import Redis from 'ioredis-mock'; -import { stringToHex } from '@polkadot/util'; -import { Bytes } from '@polkadot/types'; -import { BatchAnnouncer } from './batch.announcer'; - -// Create a mock for the dependencies -const mockConfigService = { - getIpfsCidPlaceholder: jest.fn(), -}; - -const mockBlockchainService = { - getSchema: jest.fn(), -}; - -const mockIpfsService = { - getPinned: jest.fn(), - ipfsPin: jest.fn(), -}; - -describe('BatchAnnouncer', () => { - let ipfsAnnouncer: BatchAnnouncer; - - const broadcast: FrequencyParquetSchema = [ - { - name: 'announcementType', - column_type: { - INTEGER: { - bit_width: 32, - sign: true, - }, - }, - compression: 'GZIP', - bloom_filter: false, - }, - { - name: 'contentHash', - column_type: 'BYTE_ARRAY', - compression: 'GZIP', - bloom_filter: true, - }, - { - name: 'fromId', - column_type: { - INTEGER: { - bit_width: 64, - sign: false, - }, - }, - compression: 'GZIP', - bloom_filter: true, - }, - { - name: 'url', - column_type: 'STRING', - compression: 'GZIP', - bloom_filter: false, - }, - ]; - const mockClient = new Redis(); - - beforeEach(async () => { - ipfsAnnouncer = new BatchAnnouncer(mockClient, mockConfigService as any, mockBlockchainService as any, mockIpfsService as any); - }); - it('should be defined', () => { - expect(ipfsAnnouncer).toBeDefined(); - }); - - // Write your test cases here - it('should announce a batch to IPFS', async () => { - // Mock the necessary dependencies' behavior - mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); - mockBlockchainService.getSchema.mockReturnValue({ model: Buffer.from(stringToHex(JSON.stringify(broadcast))) }); - mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); - mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', size: 10, hash: 'mockHash' }); - - const batchJob = { - batchId: 'mockBatchId', - schemaId: 123, - announcements: [], - }; - - const result = await ipfsAnnouncer.announce(batchJob); - assert(result); - expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); - expect(mockBlockchainService.getSchema).toHaveBeenCalledWith(123); - }); -}); diff --git a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts b/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts deleted file mode 100644 index ebac079c..00000000 --- a/services/content-watcher/apps/worker/src/batch_announcer/batch.announcer.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { PassThrough } from 'node:stream'; -import { ParquetWriter } from '@dsnp/parquetjs'; -import { fromFrequencySchema } from '@dsnp/frequency-schemas/parquet'; -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import Redis from 'ioredis'; -import { PalletSchemasSchema } from '@polkadot/types/lookup'; -import { hexToString } from '@polkadot/util'; -import { RedisUtils } from '../../../../libs/common/src/utils/redis'; -import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; -import { IPublisherJob } from '../interfaces/publisher-job.interface'; -import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; - -@Injectable() -export class BatchAnnouncer { - private logger: Logger; - - constructor( - @InjectRedis() private cacheManager: Redis, - private configService: ConfigService, - private blockchainService: BlockchainService, - private ipfsService: IpfsService, - ) { - this.logger = new Logger(BatchAnnouncer.name); - } - - public async announce(batchJob: IBatchAnnouncerJobData): Promise { - this.logger.debug(`Announcing batch ${batchJob.batchId} on IPFS`); - const { batchId, schemaId, announcements } = batchJob; - - const schemaCacheKey = `schema:${schemaId}`; - let cachedSchema: string | null = await this.cacheManager.get(schemaCacheKey); - if (!cachedSchema) { - const schemaResponse = await this.blockchainService.getSchema(schemaId); - cachedSchema = JSON.stringify(schemaResponse); - await this.cacheManager.setex(schemaCacheKey, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, cachedSchema); - } - - const frequencySchema: PalletSchemasSchema = JSON.parse(cachedSchema); - const hexString: string = Buffer.from(frequencySchema.model).toString('utf8'); - const schema = JSON.parse(hexToString(hexString)); - if (!schema) { - throw new Error(`Unable to parse schema for schemaId ${schemaId}`); - } - - const [parquetSchema, writerOptions] = fromFrequencySchema(schema); - const publishStream = new PassThrough(); - const parquetBufferAwait = this.bufferPublishStream(publishStream); - const writer = await ParquetWriter.openStream(parquetSchema, publishStream as any, writerOptions); - // eslint-disable-next-line no-restricted-syntax - for await (const announcement of announcements) { - await writer.appendRow(announcement); - } - await writer.close(); - - const buffer = await parquetBufferAwait; - const [cid, hash, size] = await this.pinParquetFileToIPFS(buffer); - const ipfsUrl = await this.formIpfsUrl(cid); - this.logger.debug(`Batch ${batchId} published to IPFS at ${ipfsUrl}`); - this.logger.debug(`Batch ${batchId} hash: ${hash}`); - return { id: batchId, schemaId, data: { cid, payloadLength: size } }; - } - - private async bufferPublishStream(publishStream: PassThrough): Promise { - this.logger.debug('Buffering publish stream'); - return new Promise((resolve, reject) => { - const buffers: Buffer[] = []; - publishStream.on('data', (data) => { - buffers.push(data); - }); - publishStream.on('end', () => { - resolve(Buffer.concat(buffers)); - }); - publishStream.on('error', (err) => { - reject(err); - }); - }); - } - - private async pinParquetFileToIPFS(buf: Buffer): Promise<[string, string, number]> { - const { cid, hash, size } = await this.ipfsService.ipfsPin('application/octet-stream', buf); - return [cid.toString(), hash, size]; - } - - private async formIpfsUrl(cid: string): Promise { - return this.configService.getIpfsCidPlaceholder(cid); - } -} diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts deleted file mode 100644 index 383b95b8..00000000 --- a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.module.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* -https://docs.nestjs.com/modules -*/ - -import { BullModule } from '@nestjs/bullmq'; -import { Module } from '@nestjs/common'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { ScheduleModule } from '@nestjs/schedule'; -import { ConfigModule } from '../../../../libs/common/src/config/config.module'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { QueueConstants } from '../../../../libs/common/src'; -import { BatchingProcessorService } from './batching.processor.service'; -import { BroadcastWorker } from './workers/broadcast.worker'; -import { ReplyWorker } from './workers/reply.worker'; -import { ReactionWorker } from './workers/reaction.worker'; -import { TombstoneWorker } from './workers/tombstone.worker'; -import { UpdateWorker } from './workers/update.worker'; -import { ProfileWorker } from './workers/profile.worker'; - -@Module({ - imports: [ - ConfigModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - ScheduleModule.forRoot(), - BullModule.registerQueue({ - name: QueueConstants.BATCH_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.BROADCAST_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.REPLY_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.REACTION_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.TOMBSTONE_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.UPDATE_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.PROFILE_QUEUE_NAME, - }), - ], - providers: [BatchingProcessorService, BroadcastWorker, ReplyWorker, ReactionWorker, TombstoneWorker, UpdateWorker, ProfileWorker], - exports: [BullModule, BatchingProcessorService], -}) -export class BatchingProcessorModule {} diff --git a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts b/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts deleted file mode 100644 index dc06c918..00000000 --- a/services/content-watcher/apps/worker/src/batching_processor/batching.processor.service.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Injectable, Logger } from '@nestjs/common'; -import { Job, Queue } from 'bullmq'; -import Redis from 'ioredis'; -import { InjectQueue } from '@nestjs/bullmq'; -import { SchedulerRegistry } from '@nestjs/schedule'; -import { randomUUID } from 'crypto'; -import * as fs from 'fs'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { Announcement } from '../../../../libs/common/src/interfaces/dsnp'; -import { RedisUtils } from '../../../../libs/common/src/utils/redis'; -import { IBatchMetadata } from '../../../../libs/common/src/interfaces/batch.interface'; -import getBatchMetadataKey = RedisUtils.getBatchMetadataKey; -import getBatchDataKey = RedisUtils.getBatchDataKey; -import { IBatchAnnouncerJobData } from '../interfaces/batch-announcer.job.interface'; -import { DsnpSchemas } from '../../../../libs/common/src/utils/dsnp.schema'; -import { QueueConstants } from '../../../../libs/common/src'; -import getBatchLockKey = RedisUtils.getLockKey; - -@Injectable() -export class BatchingProcessorService { - private logger: Logger; - - constructor( - @InjectRedis() private redis: Redis, - @InjectQueue(QueueConstants.BATCH_QUEUE_NAME) private outputQueue: Queue, - private schedulerRegistry: SchedulerRegistry, - private configService: ConfigService, - ) { - this.logger = new Logger(this.constructor.name); - redis.defineCommand('addToBatch', { - numberOfKeys: 2, - lua: fs.readFileSync('lua/addToBatch.lua', 'utf8'), - }); - redis.defineCommand('lockBatch', { - numberOfKeys: 4, - lua: fs.readFileSync('lua/lockBatch.lua', 'utf8'), - }); - } - - async setupActiveBatchTimeout(queueName: string) { - const metadata = await this.getMetadataFromRedis(queueName); - if (metadata) { - const openTimeMs = Math.round(Date.now() - metadata.startTimestamp); - const batchTimeoutInMs = this.configService.getBatchIntervalSeconds() * 1000; - if (openTimeMs >= batchTimeoutInMs) { - await this.closeBatch(queueName, metadata.batchId, false); - } else { - const remainingTimeMs = batchTimeoutInMs - openTimeMs; - this.addBatchTimeout(queueName, metadata.batchId, remainingTimeMs); - } - } - } - - async process(job: Job, queueName: string): Promise { - this.logger.log(`Processing job ${job.id} from ${queueName}`); - - const batchId = randomUUID().toString(); - const newMetadata = JSON.stringify({ - batchId, - startTimestamp: Date.now(), - rowCount: 1, - } as IBatchMetadata); - const newData = JSON.stringify(job.data); - - // @ts-ignore - const rowCount = await this.redis.addToBatch(getBatchMetadataKey(queueName), getBatchDataKey(queueName), newMetadata, job.id!, newData); - this.logger.log(rowCount); - if (rowCount === 1) { - this.logger.log(`Processing job ${job.id} with a new batch`); - const timeout = this.configService.getBatchIntervalSeconds() * 1000; - this.addBatchTimeout(queueName, batchId, timeout); - } else if (rowCount >= this.configService.getBatchMaxCount()) { - await this.closeBatch(queueName, batchId, false); - } else if (rowCount === -1) { - throw new Error(`invalid result from addingToBatch for job ${job.id} and queue ${queueName} ${this.configService.getBatchMaxCount()}`); - } - } - - async onCompleted(job: Job, queueName: string) { - this.logger.log(`Completed ${job.id} from ${queueName}`); - } - - private async closeBatch(queueName: string, batchId: string, timeout: boolean) { - this.logger.log(`Closing batch for ${queueName} ${batchId} ${timeout}`); - - const batchMetaDataKey = getBatchMetadataKey(queueName); - const batchDataKey = getBatchDataKey(queueName); - const lockedBatchMetaDataKey = getBatchLockKey(batchMetaDataKey); - const lockedBatchDataKey = getBatchLockKey(batchDataKey); - // @ts-ignore - const response = await this.redis.lockBatch( - batchMetaDataKey, - batchDataKey, - lockedBatchMetaDataKey, - lockedBatchDataKey, - Date.now(), - RedisUtils.BATCH_LOCK_EXPIRE_SECONDS * 1000, - ); - this.logger.debug(JSON.stringify(response)); - const status = response[0]; - - if (status === 0) { - this.logger.log(`No meta-data for closing batch ${queueName} ${batchId}. Ignore...`); - return; - } - if (status === -2) { - this.logger.log(`Previous batch is still locked ${queueName}. Ignore...`); - return; - } - if (status === -1) { - this.logger.log(`Previous batch is not closed for ${queueName} and we are going to close it first`); - } - const batch = response[1]; - const metaData: IBatchMetadata = JSON.parse(response[2]); - const announcements: Announcement[] = []; - for (let i = 0; i < batch.length; i += 2) { - const announcement: Announcement = JSON.parse(batch[i + 1]); - announcements.push(announcement); - } - if (announcements.length > 0) { - const job = { - batchId: metaData.batchId, - schemaId: DsnpSchemas.getSchemaId(this.configService.environment, QueueConstants.QUEUE_NAME_TO_ANNOUNCEMENT_MAP.get(queueName)!), - announcements, - } as IBatchAnnouncerJobData; - await this.outputQueue.add(`Batch Job - ${metaData.batchId}`, job, { jobId: metaData.batchId, removeOnFail: false, removeOnComplete: 1000 }); - } - try { - const result = await this.redis.multi().del(lockedBatchMetaDataKey).del(lockedBatchDataKey).exec(); - this.logger.debug(result); - const timeoutName = BatchingProcessorService.getTimeoutName(queueName, batchId); - if (this.schedulerRegistry.doesExist('timeout', timeoutName)) { - this.schedulerRegistry.deleteTimeout(timeoutName); - } - } catch (e) { - this.logger.error(e); - } - if (status === -1) { - this.logger.log(`after closing the previous leftover locked batch now we are going to close ${queueName}`); - await this.closeBatch(queueName, batchId, timeout); - } - } - - private async getMetadataFromRedis(queueName: string): Promise { - const batchMetadata = await this.redis.get(getBatchMetadataKey(queueName)); - return batchMetadata ? JSON.parse(batchMetadata) : undefined; - } - - // eslint-disable-next-line class-methods-use-this - private static getTimeoutName(queueName: string, batchId: string): string { - return `TIMEOUT:${queueName}:${batchId}`; - } - - private addBatchTimeout(queueName: string, batchId: string, timeoutMs: number) { - const timeoutHandler = setTimeout(async () => this.closeBatch(queueName, batchId, true), timeoutMs); - const timeoutName = BatchingProcessorService.getTimeoutName(queueName, batchId); - if (this.schedulerRegistry.doesExist('timeout', timeoutName)) { - this.schedulerRegistry.deleteTimeout(timeoutName); - } - this.schedulerRegistry.addTimeout(timeoutName, timeoutHandler); - } -} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts deleted file mode 100644 index c9881cd6..00000000 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/broadcast.worker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; -import { Job } from 'bullmq'; -import { QueueConstants } from '../../../../../libs/common/src'; -import { BatchingProcessorService } from '../batching.processor.service'; -import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; -import { BaseConsumer } from '../../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.BROADCAST_QUEUE_NAME, { concurrency: 2 }) -export class BroadcastWorker extends BaseConsumer implements OnApplicationBootstrap { - constructor(private batchingProcessorService: BatchingProcessorService) { - super(); - } - - async onApplicationBootstrap() { - return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.BROADCAST_QUEUE_NAME); - } - - async process(job: Job): Promise { - return this.batchingProcessorService.process(job, QueueConstants.BROADCAST_QUEUE_NAME); - } - - @OnWorkerEvent('completed') - async onCompleted(job: Job) { - await this.batchingProcessorService.onCompleted(job, QueueConstants.BROADCAST_QUEUE_NAME); - // calling in the end for graceful shutdowns - super.onCompleted(job); - } -} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts deleted file mode 100644 index 060a80e9..00000000 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/profile.worker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Processor, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; -import { Job } from 'bullmq'; -import { QueueConstants } from '../../../../../libs/common/src'; -import { BatchingProcessorService } from '../batching.processor.service'; -import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; -import { BaseConsumer } from '../../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.PROFILE_QUEUE_NAME, { concurrency: 2 }) -export class ProfileWorker extends BaseConsumer implements OnApplicationBootstrap { - constructor(private batchingProcessorService: BatchingProcessorService) { - super(); - } - - async onApplicationBootstrap() { - return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.PROFILE_QUEUE_NAME); - } - - async process(job: Job): Promise { - return this.batchingProcessorService.process(job, QueueConstants.PROFILE_QUEUE_NAME); - } - - @OnWorkerEvent('completed') - async onCompleted(job: Job) { - await this.batchingProcessorService.onCompleted(job, QueueConstants.PROFILE_QUEUE_NAME); - // calling in the end for graceful shutdowns - super.onCompleted(job); - } -} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts deleted file mode 100644 index 2fcf38ba..00000000 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/reaction.worker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; -import { Job } from 'bullmq'; -import { QueueConstants } from '../../../../../libs/common/src'; -import { BatchingProcessorService } from '../batching.processor.service'; -import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; -import { BaseConsumer } from '../../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.REACTION_QUEUE_NAME, { concurrency: 2 }) -export class ReactionWorker extends BaseConsumer implements OnApplicationBootstrap { - constructor(private batchingProcessorService: BatchingProcessorService) { - super(); - } - - async onApplicationBootstrap() { - return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.REACTION_QUEUE_NAME); - } - - async process(job: Job): Promise { - return this.batchingProcessorService.process(job, QueueConstants.REACTION_QUEUE_NAME); - } - - @OnWorkerEvent('completed') - async onCompleted(job: Job) { - await this.batchingProcessorService.onCompleted(job, QueueConstants.REACTION_QUEUE_NAME); - // calling in the end for graceful shutdowns - super.onCompleted(job); - } -} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts deleted file mode 100644 index 780da5a2..00000000 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/reply.worker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Processor, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; -import { Job } from 'bullmq'; -import { QueueConstants } from '../../../../../libs/common/src'; -import { BatchingProcessorService } from '../batching.processor.service'; -import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; -import { BaseConsumer } from '../../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.REPLY_QUEUE_NAME, { concurrency: 2 }) -export class ReplyWorker extends BaseConsumer implements OnApplicationBootstrap { - constructor(private batchingProcessorService: BatchingProcessorService) { - super(); - } - - async onApplicationBootstrap() { - return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.REPLY_QUEUE_NAME); - } - - async process(job: Job): Promise { - return this.batchingProcessorService.process(job, QueueConstants.REPLY_QUEUE_NAME); - } - - @OnWorkerEvent('completed') - async onCompleted(job: Job) { - await this.batchingProcessorService.onCompleted(job, QueueConstants.REPLY_QUEUE_NAME); - // calling in the end for graceful shutdowns - super.onCompleted(job); - } -} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts deleted file mode 100644 index 6d15265d..00000000 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/tombstone.worker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Processor, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; -import { Job } from 'bullmq'; -import { QueueConstants } from '../../../../../libs/common/src'; -import { BatchingProcessorService } from '../batching.processor.service'; -import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; -import { BaseConsumer } from '../../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.TOMBSTONE_QUEUE_NAME, { concurrency: 2 }) -export class TombstoneWorker extends BaseConsumer implements OnApplicationBootstrap { - constructor(private batchingProcessorService: BatchingProcessorService) { - super(); - } - - async onApplicationBootstrap() { - return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.TOMBSTONE_QUEUE_NAME); - } - - async process(job: Job): Promise { - return this.batchingProcessorService.process(job, QueueConstants.TOMBSTONE_QUEUE_NAME); - } - - @OnWorkerEvent('completed') - async onCompleted(job: Job) { - await this.batchingProcessorService.onCompleted(job, QueueConstants.TOMBSTONE_QUEUE_NAME); - // calling in the end for graceful shutdowns - super.onCompleted(job); - } -} diff --git a/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts b/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts deleted file mode 100644 index d50bd0e4..00000000 --- a/services/content-watcher/apps/worker/src/batching_processor/workers/update.worker.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; -import { Job } from 'bullmq'; -import { QueueConstants } from '../../../../../libs/common/src'; -import { BatchingProcessorService } from '../batching.processor.service'; -import { Announcement } from '../../../../../libs/common/src/interfaces/dsnp'; -import { BaseConsumer } from '../../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.UPDATE_QUEUE_NAME, { concurrency: 2 }) -export class UpdateWorker extends BaseConsumer implements OnApplicationBootstrap { - constructor(private batchingProcessorService: BatchingProcessorService) { - super(); - } - - async onApplicationBootstrap() { - return this.batchingProcessorService.setupActiveBatchTimeout(QueueConstants.UPDATE_QUEUE_NAME); - } - - async process(job: Job): Promise { - return this.batchingProcessorService.process(job, QueueConstants.UPDATE_QUEUE_NAME); - } - - @OnWorkerEvent('completed') - async onCompleted(job: Job) { - await this.batchingProcessorService.onCompleted(job, QueueConstants.UPDATE_QUEUE_NAME); - // calling in the end for graceful shutdowns - super.onCompleted(job); - } -} diff --git a/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts b/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts deleted file mode 100644 index 10cb3b07..00000000 --- a/services/content-watcher/apps/worker/src/interfaces/batch-announcer.job.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Announcement } from '../../../../libs/common/src/interfaces/dsnp'; - -export interface IBatchAnnouncerJobData { - batchId: string; - schemaId: number; - announcements: Announcement[]; -} diff --git a/services/content-watcher/apps/worker/src/interfaces/publisher-job.interface.ts b/services/content-watcher/apps/worker/src/interfaces/publisher-job.interface.ts deleted file mode 100644 index 21a00f82..00000000 --- a/services/content-watcher/apps/worker/src/interfaces/publisher-job.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface IPFSJobData { - cid: string; - payloadLength: number; -} - -export interface IPublisherJob { - id: string; - schemaId: number; - data: IPFSJobData; -} diff --git a/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts b/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts deleted file mode 100644 index 69f88f9d..00000000 --- a/services/content-watcher/apps/worker/src/interfaces/status-monitor.interface.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BlockHash, Hash } from '@polkadot/types/interfaces'; -import { IPublisherJob } from './publisher-job.interface'; - -export interface ITxMonitorJob { - id: string; - txHash: Hash; - epoch: string; - lastFinalizedBlockHash: BlockHash; - referencePublishJob: IPublisherJob; -} diff --git a/services/content-watcher/apps/worker/src/main.ts b/services/content-watcher/apps/worker/src/main.ts deleted file mode 100644 index a821ecfa..00000000 --- a/services/content-watcher/apps/worker/src/main.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NestFactory } from '@nestjs/core'; -import { WorkerModule } from './worker.module'; - -async function bootstrap() { - const app = await NestFactory.createApplicationContext(WorkerModule); - app.enableShutdownHooks(); -} -bootstrap(); diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts b/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts deleted file mode 100644 index f1d50f88..00000000 --- a/services/content-watcher/apps/worker/src/monitor/status.monitor.module.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* -https://docs.nestjs.com/modules -*/ - -import { BullModule } from '@nestjs/bullmq'; -import { Module } from '@nestjs/common'; -import { EventEmitterModule } from '@nestjs/event-emitter'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { TxStatusMonitoringService } from './tx.status.monitor.service'; -import { ConfigModule } from '../../../../libs/common/src/config/config.module'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { BlockchainModule } from '../../../../libs/common/src/blockchain/blockchain.module'; -import { QueueConstants } from '../../../../libs/common/src'; - -@Module({ - imports: [ - ConfigModule, - BlockchainModule, - EventEmitterModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue( - { - name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, - defaultJobOptions: { - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.PUBLISH_QUEUE_NAME, - }, - ), - ], - controllers: [], - providers: [TxStatusMonitoringService], - exports: [BullModule, TxStatusMonitoringService], -}) -export class StatusMonitorModule {} diff --git a/services/content-watcher/apps/worker/src/monitor/status.monitoring.spec.ts b/services/content-watcher/apps/worker/src/monitor/status.monitoring.spec.ts deleted file mode 100644 index 25938c2d..00000000 --- a/services/content-watcher/apps/worker/src/monitor/status.monitoring.spec.ts +++ /dev/null @@ -1,10 +0,0 @@ -// test file for ipfs publisher -import { describe, it, beforeEach } from '@jest/globals'; - -describe('Status Monitoring', () => { - beforeEach(async () => {}); - - describe('check status', () => { - it('should handle tx receipts', async () => {}); - }); -}); diff --git a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts b/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts deleted file mode 100644 index 11a7cea6..00000000 --- a/services/content-watcher/apps/worker/src/monitor/tx.status.monitor.service.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor, InjectQueue } from '@nestjs/bullmq'; -import { Injectable } from '@nestjs/common'; -import { Job, Queue, UnrecoverableError } from 'bullmq'; -import Redis from 'ioredis'; -import { MILLISECONDS_PER_SECOND } from 'time-constants'; -import { RegistryError } from '@polkadot/types/types'; -import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; -import { ITxMonitorJob } from '../interfaces/status-monitor.interface'; -import { QueueConstants } from '../../../../libs/common/src'; -import { SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; -import { BlockchainConstants } from '../../../../libs/common/src/blockchain/blockchain-constants'; -import { BaseConsumer } from '../BaseConsumer'; -import { IPublisherJob } from '../interfaces/publisher-job.interface'; - -@Injectable() -@Processor(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, { - concurrency: 2, -}) -export class TxStatusMonitoringService extends BaseConsumer { - constructor( - @InjectRedis() private cacheManager: Redis, - @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue: Queue, - @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, - private blockchainService: BlockchainService, - ) { - super(); - } - - async process(job: Job): Promise { - this.logger.log(`Monitoring job ${job.id} of type ${job.name}`); - try { - const numberBlocksToParse = BlockchainConstants.NUMBER_BLOCKS_TO_CRAWL; - const txCapacityEpoch = job.data.epoch; - const previousKnownBlockNumber = (await this.blockchainService.getBlock(job.data.lastFinalizedBlockHash)).block.header.number.toBigInt(); - const currentFinalizedBlockNumber = await this.blockchainService.getLatestFinalizedBlockNumber(); - const blockList: bigint[] = []; - - for (let i = previousKnownBlockNumber; i <= currentFinalizedBlockNumber && i < previousKnownBlockNumber + numberBlocksToParse; i += 1n) { - blockList.push(i); - } - const txResult = await this.blockchainService.crawlBlockListForTx(job.data.txHash, blockList, [{ pallet: 'system', event: 'ExtrinsicSuccess' }]); - - if (!txResult.found) { - if (job.attemptsMade < (job.opts.attempts ?? 3)) { - // if tx has not yet included in a block, throw error to retry till max attempts - throw new Error(`Tx not found in block list, retrying (attempts=${job.attemptsMade})`); - } else { - this.logger.warn(`Could not fetch the transaction adding to publish again! ${job.id}`); - // could not find the transaction, this might happen if transaction never gets into a block - await this.retryPublishJob(job.data.referencePublishJob); - } - } else { - // found the tx - await this.setEpochCapacity(txCapacityEpoch, BigInt(txResult.capacityWithDrawn ?? 0n)); - if (txResult.error) { - this.logger.debug(`Error found in tx result: ${JSON.stringify(txResult.error)}`); - const errorReport = await this.handleMessagesFailure(job.data.id, txResult.error); - - if (errorReport.pause) { - await this.publishQueue.pause(); - } - - if (errorReport.retry) { - await this.retryPublishJob(job.data.referencePublishJob); - } else { - throw new UnrecoverableError(`Job ${job.data.id} failed with error ${JSON.stringify(txResult.error)}`); - } - } - - if (txResult.success) { - this.logger.verbose(`Successfully found ${job.data.txHash} found in block ${txResult.blockHash}`); - } - } - return txResult; - } catch (e) { - this.logger.error(e); - throw e; - } finally { - // do some stuff - } - } - - private async handleMessagesFailure(jobId: string, moduleError: RegistryError): Promise<{ pause: boolean; retry: boolean }> { - try { - switch (moduleError.method) { - case 'TooManyMessagesInBlock': - // Re-try the job in the publish queue - return { pause: false, retry: true }; - case 'UnAuthorizedDelegate': - case 'InvalidMessageSourceAccount': - case 'InvalidSchemaId': - case 'ExceedsMaxMessagePayloadSizeBytes': - case 'InvalidPayloadLocation': - case 'UnsupportedCid': - case 'InvalidCid': - return { pause: false, retry: false }; - default: - this.logger.error(`Unknown module error ${moduleError}`); - break; - } - } catch (error) { - this.logger.error(`Error handling module error: ${error}`); - } - - // unknown error, pause the queue - return { pause: false, retry: false }; - } - - private async setEpochCapacity(epoch: string, capacityWithdrew: bigint): Promise { - const epochCapacityKey = `epochCapacity:${epoch}`; - - try { - const savedCapacity = await this.cacheManager.get(epochCapacityKey); - const epochCapacity = BigInt(savedCapacity ?? 0); - const newEpochCapacity = epochCapacity + capacityWithdrew; - - const epochDurationBlocks = await this.blockchainService.getCurrentEpochLength(); - const epochDuration = epochDurationBlocks * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; - await this.cacheManager.setex(epochCapacityKey, epochDuration, newEpochCapacity.toString()); - } catch (error) { - this.logger.error(`Error setting epoch capacity: ${error}`); - } - } - - private async retryPublishJob(publishJob: IPublisherJob) { - this.logger.debug(`Retrying job ${publishJob.id}`); - await this.publishQueue.remove(publishJob.id); - await this.publishQueue.add(`Retrying publish job - ${publishJob.id}`, publishJob, { jobId: publishJob.id }); - } -} diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.spec.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.spec.ts deleted file mode 100644 index 7cbcf65c..00000000 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -// test file for ipfs publisher -import { describe, it, beforeEach } from '@jest/globals'; -import { IPFSPublisher } from './ipfs.publisher'; - -describe('IPFSPublisher', () => { - let ipfsPublisher: IPFSPublisher; - - beforeEach(async () => {}); - - describe('publish', () => { - it('should return capacity used per epoch', async () => { - const messages = [ - { - schemaId: 1, - data: { - cid: 'QmRgJZmR6Z6yB5k9aLXjzJ6jG8L6tq4v4J9zQfDz7p3J9v', - payloadLength: 100, - }, - }, - { - schemaId: 1, - data: { - cid: 'QmRgJZmR6Z6yB5k9aLXjzJ6jG8L6tq4v4J9zQfDz7p3J9v', - payloadLength: 100, - }, - }, - ]; - }); - }); -}); diff --git a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts b/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts deleted file mode 100644 index 10150b56..00000000 --- a/services/content-watcher/apps/worker/src/publisher/ipfs.publisher.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { KeyringPair } from '@polkadot/keyring/types'; -import { ISubmittableResult } from '@polkadot/types/types'; -import { SubmittableExtrinsic } from '@polkadot/api-base/types'; -import { Hash } from '@polkadot/types/interfaces'; -import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { IPublisherJob } from '../interfaces/publisher-job.interface'; -import { createKeys } from '../../../../libs/common/src/blockchain/create-keys'; -import { NonceService } from './nonce.service'; - -@Injectable() -export class IPFSPublisher { - private logger: Logger; - - constructor( - private configService: ConfigService, - private blockchainService: BlockchainService, - private nonceService: NonceService, - ) { - this.logger = new Logger(IPFSPublisher.name); - } - - public async publish(message: IPublisherJob): Promise { - this.logger.debug(JSON.stringify(message)); - const providerKeys = createKeys(this.configService.getProviderAccountSeedPhrase()); - const tx = this.blockchainService.createExtrinsicCall({ pallet: 'messages', extrinsic: 'addIpfsMessage' }, message.schemaId, message.data.cid, message.data.payloadLength); - return this.processSingleBatch(providerKeys, tx); - } - - async processSingleBatch(providerKeys: KeyringPair, tx: SubmittableExtrinsic<'rxjs', ISubmittableResult>): Promise { - this.logger.debug(`Submitting tx of size ${tx.length}`); - try { - const ext = await this.blockchainService.createExtrinsic( - { pallet: 'frequencyTxPayment', extrinsic: 'payWithCapacity' }, - { eventPallet: 'messages', event: 'MessagesStored' }, - providerKeys, - tx, - ); - const nonce = await this.nonceService.getNextNonce(); - const [txHash] = await ext.signAndSend(nonce); - if (!txHash) { - throw new Error('Tx hash is undefined'); - } - this.logger.debug(`Tx hash: ${txHash}`); - return txHash; - } catch (e) { - this.logger.error(`Error processing batch: ${e}`); - throw e; - } - } -} diff --git a/services/content-watcher/apps/worker/src/publisher/nonce.service.ts b/services/content-watcher/apps/worker/src/publisher/nonce.service.ts deleted file mode 100644 index 798a1a35..00000000 --- a/services/content-watcher/apps/worker/src/publisher/nonce.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; -import Redis from 'ioredis'; -import fs from 'fs'; -import { createKeys } from '../../../../libs/common/src/blockchain/create-keys'; -import { RedisUtils } from '../../../../libs/common/src/utils/redis'; -import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; - -@Injectable() -export class NonceService implements OnApplicationBootstrap { - private logger: Logger; - - private accountId: Uint8Array; - - constructor( - @InjectRedis() private redis: Redis, - private blockchainService: BlockchainService, - private configService: ConfigService, - ) { - this.logger = new Logger(NonceService.name); - redis.defineCommand('incrementNonce', { - numberOfKeys: RedisUtils.NUMBER_OF_NONCE_KEYS_TO_CHECK, - lua: fs.readFileSync('lua/incrementNonce.lua', 'utf8'), - }); - } - - async onApplicationBootstrap() { - this.accountId = createKeys(this.configService.getProviderAccountSeedPhrase()).publicKey; - const nextNonce = await this.getNextNonce(); - this.logger.log(`nonce is set to ${nextNonce}`); - } - - async getNextNonce(): Promise { - const nonce = await this.blockchainService.getNonce(this.accountId); - const keys = this.getNextPossibleKeys(nonce); - // @ts-ignore - const nextNonceIndex = await this.redis.incrementNonce(...keys, keys.length, RedisUtils.NONCE_KEY_EXPIRE_SECONDS); - if (nextNonceIndex === -1) { - this.logger.warn(`nextNonce was full even with ${RedisUtils.NUMBER_OF_NONCE_KEYS_TO_CHECK} ${nonce}`); - return Number(nonce) + RedisUtils.NUMBER_OF_NONCE_KEYS_TO_CHECK; - } - const nextNonce = Number(nonce) + nextNonceIndex - 1; - this.logger.debug(`nextNonce ${nextNonce}`); - return nextNonce; - } - - // eslint-disable-next-line class-methods-use-this - getNextPossibleKeys(currentNonce: number): string[] { - const keys: string[] = []; - for (let i = 0; i < RedisUtils.NUMBER_OF_NONCE_KEYS_TO_CHECK; i += 1) { - const key = currentNonce + i; - keys.push(RedisUtils.getNonceKey(`${key}`)); - } - return keys; - } -} diff --git a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts b/services/content-watcher/apps/worker/src/publisher/publisher.module.ts deleted file mode 100644 index 6042d389..00000000 --- a/services/content-watcher/apps/worker/src/publisher/publisher.module.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* -https://docs.nestjs.com/modules -*/ - -import { BullModule } from '@nestjs/bullmq'; -import { Module } from '@nestjs/common'; -import { EventEmitterModule } from '@nestjs/event-emitter'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { PublishingService } from './publishing.service'; -import { ConfigModule } from '../../../../libs/common/src/config/config.module'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { BlockchainModule } from '../../../../libs/common/src/blockchain/blockchain.module'; -import { IPFSPublisher } from './ipfs.publisher'; -import { QueueConstants } from '../../../../libs/common/src'; -import { NonceService } from './nonce.service'; - -@Module({ - imports: [ - BlockchainModule, - ConfigModule, - EventEmitterModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue( - { - name: QueueConstants.PUBLISH_QUEUE_NAME, - defaultJobOptions: { - attempts: 1, - backoff: { - type: 'exponential', - }, - removeOnComplete: false, - removeOnFail: false, - }, - }, - { - name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: false, - removeOnFail: false, - }, - }, - ), - ], - controllers: [], - providers: [PublishingService, IPFSPublisher, NonceService], - exports: [BullModule, PublishingService, IPFSPublisher], -}) -export class PublisherModule {} diff --git a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts b/services/content-watcher/apps/worker/src/publisher/publishing.service.ts deleted file mode 100644 index 5347d7a0..00000000 --- a/services/content-watcher/apps/worker/src/publisher/publishing.service.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor, WorkerHost, OnWorkerEvent, InjectQueue } from '@nestjs/bullmq'; -import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common'; -import { Job, Queue } from 'bullmq'; -import Redis from 'ioredis'; -import { SchedulerRegistry } from '@nestjs/schedule'; -import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; -import { MILLISECONDS_PER_SECOND } from 'time-constants'; -import { BlockHash, Hash } from '@polkadot/types/interfaces'; -import { BlockchainService } from '../../../../libs/common/src/blockchain/blockchain.service'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { IPublisherJob } from '../interfaces/publisher-job.interface'; -import { IPFSPublisher } from './ipfs.publisher'; -import { CAPACITY_EPOCH_TIMEOUT_NAME, SECONDS_PER_BLOCK } from '../../../../libs/common/src/constants'; -import { QueueConstants } from '../../../../libs/common/src'; -import { ITxMonitorJob } from '../interfaces/status-monitor.interface'; -import { BaseConsumer } from '../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.PUBLISH_QUEUE_NAME, { - concurrency: 2, -}) -export class PublishingService extends BaseConsumer implements OnApplicationBootstrap, OnModuleDestroy { - private capacityExhausted = false; - - constructor( - @InjectRedis() private cacheManager: Redis, - @InjectQueue(QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME) private txReceiptQueue: Queue, - @InjectQueue(QueueConstants.PUBLISH_QUEUE_NAME) private publishQueue: Queue, - private blockchainService: BlockchainService, - private configService: ConfigService, - private ipfsPublisher: IPFSPublisher, - private schedulerRegistry: SchedulerRegistry, - private eventEmitter: EventEmitter2, - ) { - super(); - } - - public async onApplicationBootstrap() { - await this.checkCapacity(); - } - - async onModuleDestroy(): Promise { - try { - this.schedulerRegistry.deleteTimeout(CAPACITY_EPOCH_TIMEOUT_NAME); - } catch (e) { - // 💀 // - } - // calling in the end for graceful shutdowns - await super.onModuleDestroy(); - } - - async process(job: Job): Promise { - this.logger.log(`Processing job ${job.id} of type ${job.name}`); - try { - const currentBlockHash = await this.blockchainService.getLatestFinalizedBlockHash(); - const currentCapacityEpoch = await this.blockchainService.getCurrentCapacityEpoch(); - const txHash = await this.ipfsPublisher.publish(job.data); - await this.sendJobToTxReceiptQueue(job.data, txHash, currentBlockHash, currentCapacityEpoch.toString()); - this.logger.verbose(`Successfully completed job ${job.id}`); - return { success: true }; - } catch (e) { - this.logger.error(`Job ${job.id} failed (attempts=${job.attemptsMade})error: ${e}`); - if (e instanceof Error && e.message.includes('Inability to pay some fees')) { - this.eventEmitter.emit('capacity.exhausted'); - } - throw e; - } finally { - await this.checkCapacity(); - } - } - - async sendJobToTxReceiptQueue(jobData: IPublisherJob, txHash: Hash, lastFinalizedBlockHash: BlockHash, epoch: string): Promise { - const job: ITxMonitorJob = { - id: txHash.toString(), - epoch, - lastFinalizedBlockHash, - txHash, - referencePublishJob: jobData, - }; - // add a delay of 1 block to allow the tx receipt to go through before checking - const initialDelay = 1 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; - const retryDelay = 3 * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND; - await this.txReceiptQueue.add(`Receipt Job - ${job.id}`, job, { jobId: job.id, delay: initialDelay, attempts: 4, backoff: { type: 'exponential', delay: retryDelay } }); - } - - private async checkCapacity(): Promise { - const capacityLimit = this.configService.getCapacityLimit(); - const capacity = await this.blockchainService.capacityInfo(this.configService.getProviderId()); - const { remainingCapacity } = capacity; - const { currentEpoch } = capacity; - const epochCapacityKey = `epochCapacity:${currentEpoch}`; - const epochUsedCapacity = BigInt((await this.cacheManager.get(epochCapacityKey)) ?? 0); // Fetch capacity used by the service - let outOfCapacity = remainingCapacity <= 0n; - - if (!outOfCapacity) { - this.logger.debug(`Capacity remaining: ${remainingCapacity}`); - if (capacityLimit.type === 'percentage') { - const capacityLimitPercentage = BigInt(capacityLimit.value); - const capacityLimitThreshold = (capacity.totalCapacityIssued * capacityLimitPercentage) / 100n; - this.logger.debug(`Capacity limit threshold: ${capacityLimitThreshold}`); - if (epochUsedCapacity >= capacityLimitThreshold) { - outOfCapacity = true; - this.logger.warn(`Capacity threshold reached: used ${epochUsedCapacity} of ${capacityLimitThreshold}`); - } - } else if (epochUsedCapacity >= capacityLimit.value) { - outOfCapacity = true; - this.logger.warn(`Capacity threshold reached: used ${epochUsedCapacity} of ${capacityLimit.value}`); - } - } - - if (outOfCapacity) { - await this.eventEmitter.emitAsync('capacity.exhausted'); - } else { - await this.eventEmitter.emitAsync('capacity.refilled'); - } - } - - @OnEvent('capacity.exhausted', { async: true, promisify: true }) - private async handleCapacityExhausted() { - this.logger.debug('Received capacity.exhausted event'); - this.capacityExhausted = true; - await this.publishQueue.pause(); - const capacityLimit = this.configService.getCapacityLimit(); - const capacity = await this.blockchainService.capacityInfo(this.configService.getProviderId()); - - this.logger.debug(` - Capacity limit: ${JSON.stringify(capacityLimit)} - Remaining Capacity: ${JSON.stringify(capacity.remainingCapacity.toString())})}`); - - const blocksRemaining = capacity.nextEpochStart - capacity.currentBlockNumber; - try { - this.schedulerRegistry.addTimeout( - CAPACITY_EPOCH_TIMEOUT_NAME, - setTimeout(() => this.checkCapacity(), blocksRemaining * SECONDS_PER_BLOCK * MILLISECONDS_PER_SECOND), - ); - } catch (err) { - // ignore duplicate timeout - } - } - - @OnEvent('capacity.refilled', { async: true, promisify: true }) - private async handleCapacityRefilled() { - this.logger.debug('Received capacity.refilled event'); - this.capacityExhausted = false; - try { - this.schedulerRegistry.deleteTimeout(CAPACITY_EPOCH_TIMEOUT_NAME); - } catch (err) { - // ignore - } - } -} diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts deleted file mode 100644 index 5e6285ae..00000000 --- a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.spec.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { Queue } from 'bullmq'; -import { expect, describe, it, beforeEach, jest } from '@jest/globals'; -import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; -import { AnnouncementTypeDto, IRequestJob, ModifiableAnnouncementTypeDto, TagTypeDto } from '../../../../libs/common/src'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; - -const mockQueue = { - add: jest.fn(), -}; - -// Mock the ConfigService class -const mockConfigService = { - getIpfsCidPlaceholder: jest.fn(), -}; - -// Mock the IpfsService class -const mockIpfsService = { - getPinned: jest.fn(), - ipfsPin: jest.fn(), -}; - -describe('DsnpAnnouncementProcessor', () => { - let dsnpAnnouncementProcessor: DsnpAnnouncementProcessor; - let module: TestingModule; - - beforeEach(async () => { - module = await Test.createTestingModule({ - providers: [ - DsnpAnnouncementProcessor, - { provide: ConfigService, useValue: mockConfigService }, - { provide: IpfsService, useValue: mockIpfsService }, - { provide: Queue, useValue: mockQueue }, - { provide: 'BullQueue_assetQueue', useValue: mockQueue }, - { provide: 'BullQueue_broadcastQueue', useValue: mockQueue }, - { provide: 'BullQueue_replyQueue', useValue: mockQueue }, - { provide: 'BullQueue_reactionQueue', useValue: mockQueue }, - { provide: 'BullQueue_updateQueue', useValue: mockQueue }, - { provide: 'BullQueue_profileQueue', useValue: mockQueue }, - { provide: 'BullQueue_tombstoneQueue', useValue: mockQueue }, - ], - }).compile(); - - dsnpAnnouncementProcessor = module.get(DsnpAnnouncementProcessor); - }); - - it('should be defined', () => { - expect(dsnpAnnouncementProcessor).toBeDefined(); - }); - - it('should collect and queue a broadcast announcement', async () => { - // Mock the necessary dependencies' behavior - mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); - mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); - mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); - - const data: IRequestJob = { - id: '1', - announcementType: AnnouncementTypeDto.BROADCAST, - dsnpUserId: 'dsnp://123', - dependencyAttempt: 0, - content: { - content: { - content: 'mockContent', - published: '2021-01-01T00:00:00.000Z', - }, - }, - assetToMimeType: new Map(), - }; - - await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); - - expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); - expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); - }); - it('should collect and queue a reply announcement', async () => { - // Mock the necessary dependencies' behavior - mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); - mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); - mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); - - const data: IRequestJob = { - id: '2', - announcementType: AnnouncementTypeDto.REPLY, - dsnpUserId: 'dsnp://456', - dependencyAttempt: 0, - content: { - content: { - content: 'mockReplyContent', - published: '2021-01-01T00:00:00.000Z', - }, - inReplyTo: 'dsnp://123/reply/1', - }, - assetToMimeType: new Map(), - }; - - await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); - - expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); - expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); - }); - - it('should collect and queue a reaction announcement', async () => { - // Mock the necessary dependencies' behavior - mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); - mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); - mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); - - const data: IRequestJob = { - id: '3', - announcementType: AnnouncementTypeDto.REACTION, - dsnpUserId: 'dsnp://789', - dependencyAttempt: 0, - content: { - emoji: '👍', - inReplyTo: 'dsnp://123/reply/1', - apply: 10, - }, - assetToMimeType: new Map(), - }; - - await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); - - expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); - expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); - }); - it('should collect and queue an update announcement', async () => { - // Mock the necessary dependencies' behavior - mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); - mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); - mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); - - const data: IRequestJob = { - id: '4', - announcementType: AnnouncementTypeDto.UPDATE, - dsnpUserId: 'dsnp://101', - dependencyAttempt: 0, - content: { - content: { - content: 'mockUpdateContent', - published: '2021-01-01T00:00:00.000Z', - }, - targetAnnouncementType: ModifiableAnnouncementTypeDto.REPLY, - targetContentHash: 'dsnp://123/reply/1', - }, - assetToMimeType: new Map(), - }; - - await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); - - expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); - expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); - }); - - it('should collect and queue a profile announcement', async () => { - // Mock the necessary dependencies' behavior - mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); - mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); - mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); - - const data: IRequestJob = { - id: '5', - announcementType: AnnouncementTypeDto.PROFILE, - dsnpUserId: 'dsnp://789', - dependencyAttempt: 0, - content: { - profile: { - name: 'John Doe', - published: '2021-01-01T00:00:00.000Z', - summary: 'A brief summary', - tag: [ - { type: TagTypeDto.Hashtag, name: 'tag1' }, - { type: TagTypeDto.Mention, name: 'user1', mentionedId: 'dsnp://123' }, - ], - }, - }, - assetToMimeType: new Map(), - }; - - await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); - - expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); - expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); - }); - - it('should collect and queue a tombstone announcement', async () => { - // Mock the necessary dependencies' behavior - mockConfigService.getIpfsCidPlaceholder.mockReturnValue('mockIpfsUrl'); - mockIpfsService.getPinned.mockReturnValue(Buffer.from('mockContentBuffer')); - mockIpfsService.ipfsPin.mockReturnValue({ cid: 'mockCid', hash: 'mockHash', size: 123 }); - - const data: IRequestJob = { - id: '6', - announcementType: AnnouncementTypeDto.TOMBSTONE, - dsnpUserId: 'dsnp://999', - dependencyAttempt: 0, - content: { - targetAnnouncementType: ModifiableAnnouncementTypeDto.BROADCAST, - targetContentHash: 'dsnp://123/broadcast/1', - }, - assetToMimeType: new Map(), - }; - - await dsnpAnnouncementProcessor.collectAnnouncementAndQueue(data); - - expect(mockConfigService.getIpfsCidPlaceholder).toHaveBeenCalledWith('mockCid'); - expect(mockIpfsService.ipfsPin).toHaveBeenCalledWith('application/octet-stream', expect.any(Buffer)); - }); -}); diff --git a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts b/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts deleted file mode 100644 index 68fa4966..00000000 --- a/services/content-watcher/apps/worker/src/request_processor/dsnp.announcement.processor.ts +++ /dev/null @@ -1,414 +0,0 @@ -import { - ActivityContentTag, - ActivityContentAttachment, - ActivityContentLink, - ActivityContentImageLink, - ActivityContentImage, - ActivityContentVideoLink, - ActivityContentVideo, - ActivityContentAudioLink, - ActivityContentAudio, - ActivityContentProfile, -} from '@dsnp/activity-content/types'; -import { Injectable, Logger } from '@nestjs/common'; -import { InjectQueue } from '@nestjs/bullmq'; -import { Queue } from 'bullmq'; -import { - TagTypeDto, - AssetDto, - AttachmentTypeDto, - IRequestJob, - QueueConstants, - BroadcastDto, - ProfileDto, - ReactionDto, - ReplyDto, - UpdateDto, - AnnouncementTypeDto, - TombstoneDto, - ModifiableAnnouncementTypeDto, -} from '../../../../libs/common/src'; -import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { calculateDsnpHash } from '../../../../libs/common/src/utils/ipfs'; -import { - AnnouncementType, - BroadcastAnnouncement, - ProfileAnnouncement, - ReactionAnnouncement, - ReplyAnnouncement, - UpdateAnnouncement, - createBroadcast, - createNote, - createProfile, - createReaction, - createReply, - createTombstone, - createUpdate, -} from '../../../../libs/common/src/interfaces/dsnp'; - -@Injectable() -export class DsnpAnnouncementProcessor { - private logger: Logger; - - constructor( - @InjectQueue(QueueConstants.BROADCAST_QUEUE_NAME) private broadcastQueue: Queue, - @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, - @InjectQueue(QueueConstants.REACTION_QUEUE_NAME) private reactionQueue: Queue, - @InjectQueue(QueueConstants.UPDATE_QUEUE_NAME) private updateQueue: Queue, - @InjectQueue(QueueConstants.PROFILE_QUEUE_NAME) private profileQueue: Queue, - @InjectQueue(QueueConstants.TOMBSTONE_QUEUE_NAME) private tombstoneQueue: Queue, - private configService: ConfigService, - private ipfsService: IpfsService, - ) { - this.logger = new Logger(DsnpAnnouncementProcessor.name); - } - - public async collectAnnouncementAndQueue(data: IRequestJob) { - this.logger.debug(`Collecting announcement and queuing`); - this.logger.verbose(`Processing Activity: ${data.announcementType} for ${data.dsnpUserId}`); - try { - switch (data.announcementType) { - case AnnouncementTypeDto.BROADCAST: - await this.queueBroadcast(data); - break; - case AnnouncementTypeDto.REPLY: - await this.queueReply(data); - break; - case AnnouncementTypeDto.REACTION: - await this.queueReaction(data); - break; - case AnnouncementTypeDto.UPDATE: - await this.queueUpdate(data); - break; - case AnnouncementTypeDto.PROFILE: - await this.queueProfile(data); - break; - case AnnouncementTypeDto.TOMBSTONE: - await this.queueTombstone(data); - break; - default: - throw new Error(`Unsupported announcement type ${typeof data.announcementType}`); - } - } catch (e) { - this.logger.error(`Error processing announcement ${data.announcementType} for ${data.dsnpUserId}: ${e}`); - throw e; - } - } - - private async queueBroadcast(data: IRequestJob) { - const broadcast = await this.processBroadcast(data.content as BroadcastDto, data.dsnpUserId, data.assetToMimeType); - await this.broadcastQueue.add(`Broadcast Job - ${data.id}`, broadcast, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); - } - - private async queueReply(data: IRequestJob) { - const reply = await this.processReply(data.content as ReplyDto, data.dsnpUserId, data.assetToMimeType); - await this.replyQueue.add(`Reply Job - ${data.id}`, reply, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); - } - - private async queueReaction(data: IRequestJob) { - const reaction = await this.processReaction(data.content as ReactionDto, data.dsnpUserId); - await this.reactionQueue.add(`Reaction Job - ${data.id}`, reaction, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); - } - - private async queueUpdate(data: IRequestJob) { - const updateDto = data.content as UpdateDto; - const updateAnnouncementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(updateDto.targetAnnouncementType); - const update = await this.processUpdate(updateDto, updateAnnouncementType, updateDto.targetContentHash ?? '', data.dsnpUserId, data.assetToMimeType); - await this.updateQueue.add(`Update Job - ${data.id}`, update, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); - } - - private async queueProfile(data: IRequestJob) { - const profile = await this.processProfile(data.content as ProfileDto, data.dsnpUserId, data.assetToMimeType); - await this.profileQueue.add(`Profile Job - ${data.id}`, profile, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); - } - - private async queueTombstone(data: IRequestJob) { - const tombStoneDto = data.content as TombstoneDto; - const announcementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(tombStoneDto.targetAnnouncementType); - const tombstone = createTombstone(data.dsnpUserId, announcementType, tombStoneDto.targetContentHash ?? ''); - await this.tombstoneQueue.add(`Tombstone Job - ${data.id}`, tombstone, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); - } - - private async getAnnouncementTypeFromModifiableAnnouncementType(modifiableAnnouncementType: ModifiableAnnouncementTypeDto): Promise { - this.logger.debug(`Getting announcement type from modifiable announcement type`); - switch (modifiableAnnouncementType) { - case ModifiableAnnouncementTypeDto.BROADCAST: - return AnnouncementType.Broadcast; - case ModifiableAnnouncementTypeDto.REPLY: - return AnnouncementType.Reply; - default: - throw new Error(`Unsupported announcement type ${typeof modifiableAnnouncementType}`); - } - } - - public async prepareNote(noteContent: any, assetToMimeType?: Map): Promise<[string, string, string]> { - this.logger.debug(`Preparing note`); - const tags: ActivityContentTag[] = this.prepareTags(noteContent?.content.tag); - const attachments: ActivityContentAttachment[] = await this.prepareAttachments(noteContent.content.assets, assetToMimeType); - - const note = createNote(noteContent.content.content ?? '', new Date(noteContent.content.published ?? ''), { - name: noteContent.content.name, - location: this.prepareLocation(noteContent.content.location), - tag: tags, - attachment: attachments, - }); - const noteString = JSON.stringify(note); - const [cid, hash] = await this.pinBufferToIPFS(Buffer.from(noteString)); - const ipfsUrl = this.formIpfsUrl(cid); - return [cid, ipfsUrl, hash]; - } - - private prepareTags(tagData?: any[]): ActivityContentTag[] { - this.logger.debug(`Preparing tags`); - const tags: ActivityContentTag[] = []; - if (tagData) { - tagData.forEach((tag) => { - switch (tag.type) { - case TagTypeDto.Hashtag: - tags.push({ name: tag.name }); - break; - case TagTypeDto.Mention: - tags.push({ - name: tag.name, - type: 'Mention', - id: tag.mentionedId, - }); - break; - default: - throw new Error(`Unsupported tag type ${typeof tag.type}`); - } - }); - } - return tags; - } - - private async prepareAttachments(assetData?: any[], assetToMimeType?: Map): Promise { - const attachments: ActivityContentAttachment[] = []; - if (assetData) { - const promises = assetData.map(async (asset) => { - switch (asset.type) { - case AttachmentTypeDto.LINK: - attachments.push(this.prepareLinkAttachment(asset)); - break; - case AttachmentTypeDto.IMAGE: - attachments.push(await this.prepareImageAttachment(asset, assetToMimeType)); - break; - case AttachmentTypeDto.VIDEO: - attachments.push(await this.prepareVideoAttachment(asset, assetToMimeType)); - break; - case AttachmentTypeDto.AUDIO: - attachments.push(await this.prepareAudioAttachment(asset, assetToMimeType)); - break; - default: - throw new Error(`Unsupported attachment type ${typeof asset.type}`); - } - }); - await Promise.all(promises); - } - - return attachments; - } - - private prepareLinkAttachment(asset: AssetDto): ActivityContentLink { - this.logger.debug(`Preparing link attachment`); - return { - type: 'Link', - href: asset.href || '', - name: asset.name, - }; - } - - private async prepareImageAttachment(asset: AssetDto, assetToMimeType?: Map): Promise { - const imageLinks: ActivityContentImageLink[] = []; - if (asset.references) { - const promises = asset.references.map(async (reference) => { - if (!assetToMimeType) { - throw new Error(`asset ${reference.referenceId} should have a mimeTypes`); - } - const mediaType = assetToMimeType[reference.referenceId]; - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - const image: ActivityContentImageLink = { - mediaType, - hash: [hashedContent], - height: reference.height, - width: reference.width, - type: 'Link', - href: this.formIpfsUrl(reference.referenceId), - }; - imageLinks.push(image); - }); - await Promise.all(promises); - } - - return { - type: 'Image', - name: asset.name, - url: imageLinks, - }; - } - - private async prepareVideoAttachment(asset: AssetDto, assetToMimeType?: Map): Promise { - const videoLinks: ActivityContentVideoLink[] = []; - let duration: string | undefined = ''; - - if (asset.references) { - const promises = asset.references.map(async (reference) => { - if (!assetToMimeType) { - throw new Error(`asset ${reference.referenceId} should have a mimeTypes`); - } - const mediaType = assetToMimeType[reference.referenceId]; - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - const video: ActivityContentVideoLink = { - mediaType, - hash: [hashedContent], - height: reference.height, - width: reference.width, - type: 'Link', - href: this.formIpfsUrl(reference.referenceId), - }; - duration = duration ? reference.duration : ''; - videoLinks.push(video); - }); - await Promise.all(promises); - } - - return { - type: 'Video', - name: asset.name, - url: videoLinks, - duration: duration ?? undefined, - }; - } - - private async prepareAudioAttachment(asset: AssetDto, assetToMimeType?: Map): Promise { - const audioLinks: ActivityContentAudioLink[] = []; - let duration = ''; - if (asset.references) { - const promises = asset.references.map(async (reference) => { - if (!assetToMimeType) { - throw new Error(`asset ${reference.referenceId} should have a mimeTypes`); - } - const mediaType = assetToMimeType[reference.referenceId]; - const contentBuffer = await this.ipfsService.getPinned(reference.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - duration = duration ?? reference.duration ?? ''; - const audio: ActivityContentAudioLink = { - mediaType, - hash: [hashedContent], - type: 'Link', - href: this.formIpfsUrl(reference.referenceId), - }; - audioLinks.push(audio); - }); - - await Promise.all(promises); - } - - return { - type: 'Audio', - name: asset.name, - url: audioLinks, - duration, - }; - } - - private async processBroadcast(content: BroadcastDto, dsnpUserId: string, assetToMimeType?: Map): Promise { - this.logger.debug(`Processing broadcast`); - const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); - return createBroadcast(dsnpUserId, ipfsUrl, hash); - } - - private async processReply(content: ReplyDto, dsnpUserId: string, assetToMimeType?: Map): Promise { - this.logger.debug(`Processing reply for ${content.inReplyTo}`); - const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); - return createReply(dsnpUserId, ipfsUrl, hash, content.inReplyTo); - } - - private async processReaction(content: ReactionDto, dsnpUserId: string): Promise { - this.logger.debug(`Processing reaction ${content.emoji} for ${content.inReplyTo}`); - return createReaction(dsnpUserId, content.emoji, content.inReplyTo, content.apply); - } - - private async processUpdate( - content: UpdateDto, - targetAnnouncementType: AnnouncementType, - targetContentHash: string, - dsnpUserId: string, - assetToMimeType?: Map, - ): Promise { - this.logger.debug(`Processing update`); - const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); - return createUpdate(dsnpUserId, ipfsUrl, hash, targetAnnouncementType, targetContentHash); - } - - private async processProfile(content: ProfileDto, dsnpUserId: string, assetToMimeType?: Map): Promise { - this.logger.debug(`Processing profile`); - const attachments: ActivityContentImageLink[] = await this.prepareProfileIconAttachments(content.profile.icon ?? [], assetToMimeType); - - const profileActivity: ActivityContentProfile = { - '@context': 'https://www.w3.org/ns/activitystreams', - type: 'Profile', - name: content.profile.name, - published: content.profile.published, - location: this.prepareLocation(content.profile.location), - summary: content.profile.summary, - icon: attachments, - tag: this.prepareTags(content.profile.tag), - }; - const profileString = JSON.stringify(profileActivity); - const [cid, hash] = await this.pinBufferToIPFS(Buffer.from(profileString)); - return createProfile(dsnpUserId, this.formIpfsUrl(cid), hash); - } - - private async prepareProfileIconAttachments(icons: any[], assetToMimeType?: Map): Promise { - const attachments: ActivityContentImageLink[] = []; - - const promises = icons.map(async (icon) => { - if (!assetToMimeType) { - throw new Error(`asset ${icon.referenceId} should have a mimeTypes`); - } - const mediaType = assetToMimeType[icon.referenceId]; - const contentBuffer = await this.ipfsService.getPinned(icon.referenceId); - const hashedContent = await calculateDsnpHash(contentBuffer); - const image: ActivityContentImageLink = { - mediaType, - hash: [hashedContent], - height: icon.height, - width: icon.width, - type: 'Link', - href: this.formIpfsUrl(icon.referenceId), - }; - attachments.push(image); - }); - await Promise.all(promises); - - return attachments; - } - - private prepareLocation(locationData: any): any { - this.logger.debug(`Preparing location`); - if (!locationData) return null; - return { - latitude: locationData.latitude, - longitude: locationData.longitude, - radius: locationData.radius, - altitude: locationData.altitude, - accuracy: locationData.accuracy, - name: locationData.name, - units: locationData.units, - type: 'Place', - }; - } - - private async pinBufferToIPFS(buf: Buffer): Promise<[string, string, number]> { - const { cid, hash, size } = await this.ipfsService.ipfsPin('application/octet-stream', buf); - return [cid.toString(), hash, size]; - } - - private formIpfsUrl(cid: string): string { - return this.configService.getIpfsCidPlaceholder(cid); - } -} diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts deleted file mode 100644 index cabbb2eb..00000000 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.module.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* -https://docs.nestjs.com/modules -*/ - -import { BullModule } from '@nestjs/bullmq'; -import { Module } from '@nestjs/common'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { ConfigModule } from '../../../../libs/common/src/config/config.module'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { QueueConstants } from '../../../../libs/common/src'; -import { RequestProcessorService } from './request.processor.service'; -import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; -import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; - -@Module({ - imports: [ - ConfigModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue({ - name: QueueConstants.ASSET_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.REQUEST_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.BROADCAST_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.REPLY_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.REACTION_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.TOMBSTONE_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.UPDATE_QUEUE_NAME, - }), - BullModule.registerQueue({ - name: QueueConstants.PROFILE_QUEUE_NAME, - }), - ], - providers: [RequestProcessorService, IpfsService, DsnpAnnouncementProcessor], - exports: [BullModule, RequestProcessorService, IpfsService, DsnpAnnouncementProcessor], -}) -export class RequestProcessorModule {} diff --git a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts b/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts deleted file mode 100644 index c507365a..00000000 --- a/services/content-watcher/apps/worker/src/request_processor/request.processor.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { Processor } from '@nestjs/bullmq'; -import { Injectable } from '@nestjs/common'; -import { DelayedError, Job } from 'bullmq'; -import Redis from 'ioredis'; -import { ConfigService } from '../../../../libs/common/src/config/config.service'; -import { IRequestJob, QueueConstants } from '../../../../libs/common/src'; -import { IpfsService } from '../../../../libs/common/src/utils/ipfs.client'; -import { DsnpAnnouncementProcessor } from './dsnp.announcement.processor'; -import { BaseConsumer } from '../BaseConsumer'; - -@Injectable() -@Processor(QueueConstants.REQUEST_QUEUE_NAME) -export class RequestProcessorService extends BaseConsumer { - constructor( - @InjectRedis() private cacheManager: Redis, - private dsnpAnnouncementProcessor: DsnpAnnouncementProcessor, - private configService: ConfigService, - private ipfsService: IpfsService, - ) { - super(); - } - - async process(job: Job): Promise { - this.logger.log(`Processing job ${job.id} of type ${job.name}`); - this.logger.debug(job.asJSON()); - try { - const assets: string[] = job.data.assetToMimeType ? Object.keys(job.data.assetToMimeType) : []; - const pinnedAssets = assets.map((cid) => this.ipfsService.getPinned(cid)); - const pinnedResult = await Promise.all(pinnedAssets); - // if any of assets does not exist delay the job for a future attempt - if (pinnedResult.some((buffer) => !buffer || buffer.length === 0)) { - await this.delayJobAndIncrementAttempts(job); - } else { - await this.dsnpAnnouncementProcessor.collectAnnouncementAndQueue(job.data); - } - } catch (e) { - this.logger.error(e); - throw e; - } - } - - // eslint-disable-next-line class-methods-use-this - private async delayJobAndIncrementAttempts(job: Job) { - const { data } = job; - data.dependencyAttempt += 1; - if (data.dependencyAttempt <= 3) { - // exponential backoff - const delayedTime = 2 ** data.dependencyAttempt * this.configService.getAssetUploadVerificationDelaySeconds() * 1000; - await job.moveToDelayed(Date.now() + delayedTime, job.token); - await job.update(data); - throw new DelayedError(); - } else { - throw new Error('Dependency failed!'); - } - } -} diff --git a/services/content-watcher/apps/worker/src/worker.module.ts b/services/content-watcher/apps/worker/src/worker.module.ts deleted file mode 100644 index b70e14a2..00000000 --- a/services/content-watcher/apps/worker/src/worker.module.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Module } from '@nestjs/common'; -import { BullModule } from '@nestjs/bullmq'; -import { ScheduleModule } from '@nestjs/schedule'; -import { EventEmitterModule } from '@nestjs/event-emitter'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { PublishingService } from './publisher/publishing.service'; -import { PublisherModule } from './publisher/publisher.module'; -import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; -import { BatchAnnouncementService } from './batch_announcer/batch.announcer.service'; -import { BatchAnnouncerModule } from './batch_announcer/batch.announcer.module'; -import { StatusMonitorModule } from './monitor/status.monitor.module'; -import { TxStatusMonitoringService } from './monitor/tx.status.monitor.service'; -import { AssetProcessorModule } from './asset_processor/asset.processor.module'; -import { AssetProcessorService } from './asset_processor/asset.processor.service'; -import { RequestProcessorModule } from './request_processor/request.processor.module'; -import { RequestProcessorService } from './request_processor/request.processor.service'; -import { BatchingProcessorModule } from './batching_processor/batching.processor.module'; -import { ConfigModule } from '../../../libs/common/src/config/config.module'; -import { ConfigService } from '../../../libs/common/src/config/config.service'; - -@Module({ - imports: [ - BullModule, - ConfigModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - EventEmitterModule.forRoot({ - // Use this instance throughout the application - global: true, - // set this to `true` to use wildcards - wildcard: false, - // the delimiter used to segment namespaces - delimiter: '.', - // set this to `true` if you want to emit the newListener event - newListener: false, - // set this to `true` if you want to emit the removeListener event - removeListener: false, - // the maximum amount of listeners that can be assigned to an event - maxListeners: 10, - // show event name in memory leak message when more than maximum amount of listeners is assigned - verboseMemoryLeak: false, - // disable throwing uncaughtException if an error event is emitted and it has no listeners - ignoreErrors: false, - }), - ScheduleModule.forRoot(), - PublisherModule, - BlockchainModule, - BatchAnnouncerModule, - StatusMonitorModule, - AssetProcessorModule, - RequestProcessorModule, - BatchingProcessorModule, - ], - providers: [BatchAnnouncementService, ConfigService, PublishingService, TxStatusMonitoringService, AssetProcessorService, RequestProcessorService], -}) -export class WorkerModule {} diff --git a/services/content-watcher/apps/worker/tsconfig.app.json b/services/content-watcher/apps/worker/tsconfig.app.json deleted file mode 100644 index fc3f5ed8..00000000 --- a/services/content-watcher/apps/worker/tsconfig.app.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "declaration": false, - "outDir": "../../dist/apps/worker" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] -} diff --git a/services/content-watcher/docker-compose.dev.yaml b/services/content-watcher/docker-compose.dev.yaml index 6f69eb3e..73708a12 100644 --- a/services/content-watcher/docker-compose.dev.yaml +++ b/services/content-watcher/docker-compose.dev.yaml @@ -50,24 +50,6 @@ services: networks: - content-watcher-service - content-watcher-service-worker: - build: - context: . - dockerfile: dev.Dockerfile - env_file: - - .env.docker.dev - environment: - - START_PROCESS=worker - - REDIS_URL=redis://redis:6379 - volumes: - - ./:/app - depends_on: - - redis - - frequency - - kubo_ipfs - networks: - - content-watcher-service - volumes: redis_data: ipfs_data: diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 9c8024ab..d616055c 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -9,14 +9,11 @@ "generate-swagger-ui": "redoc-cli bundle swagger.yaml --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "start:api": "nest start api", - "start:worker": "nest start worker", "start:api:dev": "set -a ; . .env ; nest start api", - "start:worker:dev": "set -a ; . .env ; nest start worker", "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", - "start:worker:debug": "set -a ; . .env ;nest start worker --debug --watch", "docker-build": "docker build -t content-watcher-service .", "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", - "docker-run": "docker build -t content-watcher-service-deploy . ; docker run -p 6379:6379 --env-file .env content-watcher-service-deploy", + "docker-run": " build -t content-watcher-service-deploy . ; docker run -p 6379:6379 --env-file .env content-watcher-service-deploy", "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs -f content-watcher-service", "docker-stop:dev": "docker-compose -f docker-compose.dev.yaml stop", "clean": "rm -Rf dist", From d82007d9bd47877e8bb23537f17bca71eeebaa1a Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Wed, 11 Oct 2023 09:45:51 -0500 Subject: [PATCH 040/137] remove old apis --- .../apps/api/src/api.controller.ts | 69 ---------- .../apps/api/src/api.module.ts | 3 +- .../apps/api/src/api.service.ts | 130 ------------------ .../apps/api/src/development.controller.ts | 101 -------------- 4 files changed, 1 insertion(+), 302 deletions(-) delete mode 100644 services/content-watcher/apps/api/src/development.controller.ts diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index add2a11d..11c375c4 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -33,73 +33,4 @@ export class ApiController { status: HttpStatus.OK, }; } - - @Put('asset/upload') - @UseInterceptors(FilesInterceptor('files')) - @HttpCode(202) - @ApiConsumes('multipart/form-data') - @ApiBody({ - description: 'Asset files', - type: FilesUploadDto, - }) - async assetUpload( - @UploadedFiles( - new ParseFilePipeBuilder() - .addFileTypeValidator({ - fileType: DSNP_VALID_MIME_TYPES, - }) - // TODO: add a validator to check overall uploaded size - .addMaxSizeValidator({ - // this is in bytes (2 GB) - maxSize: parseInt(process.env.FILE_UPLOAD_MAX_SIZE_IN_BYTES!, 10), - }) - .build({ - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - files: // eslint-disable-next-line no-undef - Array, - ): Promise { - return this.apiService.addAssets(files); - } - - @Post('content/:userDsnpId/broadcast') - @HttpCode(202) - async broadcast(@Param() userDsnpId: DsnpUserIdParam, @Body() broadcastDto: BroadcastDto): Promise { - const metadata = await this.apiService.validateAssetsAndFetchMetadata(broadcastDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.BROADCAST, userDsnpId.userDsnpId, broadcastDto, metadata); - } - - @Post('content/:userDsnpId/reply') - @HttpCode(202) - async reply(@Param() userDsnpId: DsnpUserIdParam, @Body() replyDto: ReplyDto): Promise { - const metadata = await this.apiService.validateAssetsAndFetchMetadata(replyDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.REPLY, userDsnpId.userDsnpId, replyDto, metadata); - } - - @Post('content/:userDsnpId/reaction') - @HttpCode(202) - async reaction(@Param() userDsnpId: DsnpUserIdParam, @Body() reactionDto: ReactionDto): Promise { - return this.apiService.enqueueRequest(AnnouncementTypeDto.REACTION, userDsnpId.userDsnpId, reactionDto); - } - - @Put('content/:userDsnpId') - @HttpCode(202) - async update(@Param() userDsnpId: DsnpUserIdParam, @Body() updateDto: UpdateDto): Promise { - const metadata = await this.apiService.validateAssetsAndFetchMetadata(updateDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.UPDATE, userDsnpId.userDsnpId, updateDto, metadata); - } - - @Delete('content/:userDsnpId') - @HttpCode(202) - async delete(@Param() userDsnpId: DsnpUserIdParam, @Body() tombstoneDto: TombstoneDto): Promise { - return this.apiService.enqueueRequest(AnnouncementTypeDto.TOMBSTONE, userDsnpId.userDsnpId, tombstoneDto); - } - - @Put('profile/:userDsnpId') - @HttpCode(202) - async profile(@Param() userDsnpId: DsnpUserIdParam, @Body() profileDto: ProfileDto): Promise { - const metadata = await this.apiService.validateAssetsAndFetchMetadata(profileDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeDto.PROFILE, userDsnpId.userDsnpId, profileDto, metadata); - } } diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 955812e2..64091590 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -7,7 +7,6 @@ import { BullBoardModule } from '@bull-board/nestjs'; import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; import { ExpressAdapter } from '@bull-board/express'; import { ApiController } from './api.controller'; -import { DevelopmentController } from './development.controller'; import { QueueConstants } from '../../../libs/common/src'; import { ApiService } from './api.service'; import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; @@ -163,7 +162,7 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; ScheduleModule.forRoot(), ], providers: [ConfigService, ApiService, IpfsService], - controllers: process.env?.ENVIRONMENT === 'dev' ? [DevelopmentController, ApiController] : [ApiController], + controllers: [ApiController], exports: [], }) export class ApiModule {} diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 1c3b78e6..09ce871d 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -19,8 +19,6 @@ import { import { calculateIpfsCID } from '../../../libs/common/src/utils/ipfs'; import { IAssetJob, IAssetMetadata } from '../../../libs/common/src/interfaces/asset-job.interface'; import { RedisUtils } from '../../../libs/common/src/utils/redis'; -import getAssetDataKey = RedisUtils.getAssetDataKey; -import getAssetMetadataKey = RedisUtils.getAssetMetadataKey; @Injectable() export class ApiService { @@ -34,134 +32,6 @@ export class ApiService { this.logger = new Logger(this.constructor.name); } - async enqueueRequest( - announcementType: AnnouncementTypeDto, - dsnpUserId: string, - content: RequestTypeDto, - assetToMimeType?: Map, - ): Promise { - const data = { - content, - id: '', - announcementType, - dsnpUserId, - dependencyAttempt: 0, - } as IRequestJob; - data.id = this.calculateJobId(data); - if (assetToMimeType) { - // not used in id calculation since the order in map might not be deterministic - data.assetToMimeType = assetToMimeType; - } - const job = await this.requestQueue.add(`Request Job - ${data.id}`, data, { jobId: data.id, removeOnFail: false, removeOnComplete: 2000 }); // TODO: should come from queue configs - this.logger.debug(job); - return { - referenceId: data.id, - }; - } - - async validateAssetsAndFetchMetadata(content: AssetIncludedRequestDto): Promise | undefined> { - const checkingList: Array<{ onlyImage: boolean; referenceId: string }> = []; - if (content.profile) { - content.profile.icon?.forEach((reference) => checkingList.push({ onlyImage: true, referenceId: reference.referenceId })); - } else if (content.content) { - content.content.assets?.forEach( - (asset) => - asset.references?.forEach((reference) => - checkingList.push({ - onlyImage: false, - referenceId: reference.referenceId, - }), - ), - ); - } - - const redisResults = await Promise.all(checkingList.map((obj) => this.redis.get(getAssetMetadataKey(obj.referenceId)))); - const errors: string[] = []; - const map = new Map(); - redisResults.forEach((res, index) => { - if (res === null) { - errors.push(`${content.profile ? 'profile.icon' : 'content.assets'}.referenceId ${checkingList[index].referenceId} does not exist!`); - } else { - const metadata: IAssetMetadata = JSON.parse(res); - map[checkingList[index].referenceId] = metadata.mimeType; - - // checks if attached asset is an image - if (checkingList[index].onlyImage && !isImage(metadata.mimeType)) { - errors.push(`profile.icon.referenceId ${checkingList[index].referenceId} is not an image!`); - } - } - }); - if (errors.length > 0) { - throw new HttpErrorByCode[400](errors); - } - return map; - } - - // eslint-disable-next-line no-undef - async addAssets(files: Array): Promise { - // calculate ipfs cid references - const referencePromises: Promise[] = files.map((file) => calculateIpfsCID(file.buffer)); - const references = await Promise.all(referencePromises); - - let dataTransaction = this.redis.multi(); - let metadataTransaction = this.redis.multi(); - const jobs: any[] = []; - files.forEach((f, index) => { - // adding data and metadata to the transaction - dataTransaction = dataTransaction.setex(getAssetDataKey(references[index]), RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, f.buffer); - metadataTransaction = metadataTransaction.setex( - getAssetMetadataKey(references[index]), - RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, - JSON.stringify({ - ipfsCid: references[index], - mimeType: f.mimetype, - createdOn: Date.now(), - } as IAssetMetadata), - ); - // adding asset job to the jobs - jobs.push({ - name: `Asset Job - ${references[index]}`, - data: { - ipfsCid: references[index], - contentLocation: getAssetDataKey(references[index]), - metadataLocation: getAssetMetadataKey(references[index]), - mimeType: f.mimetype, - } as IAssetJob, - opts: { - jobId: references[index], - removeOnFail: false, - removeOnComplete: true, - attempts: 3, - backoff: { - type: 'exponential', - delay: 10000, - }, - } as BulkJobOptions, - }); - }); - - // currently we are applying 3 different transactions on redis - // 1: Storing the content data - // 2: Adding asset jobs - // 3: Storing the content metadata - // even though all these transactions are applied separately, the overall behavior will clean up any partial failures eventually - // partial failure scenarios: - // 1: adding jobs failure: at this point we already successfully stored the data content in redis, but since all - // of this stored data has expire-time, it would eventually get cleaned up - // 2: metadata transaction failure: at this point we already stored the data content and jobs and those two are - // enough to process the asset on the worker side, the worker will clean up both of them after processing - const dataOps = await dataTransaction.exec(); - this.checkTransactionResult(dataOps); - const queuedJobs = await this.assetQueue.addBulk(jobs); - this.logger.debug(queuedJobs); - const metaDataOps = await metadataTransaction.exec(); - this.checkTransactionResult(metaDataOps); - - return { - assetIds: references, - }; - } - // eslint-disable-next-line class-methods-use-this private calculateJobId(jobWithoutId: IRequestJob): string { const stringVal = JSON.stringify(jobWithoutId); diff --git a/services/content-watcher/apps/api/src/development.controller.ts b/services/content-watcher/apps/api/src/development.controller.ts deleted file mode 100644 index 79c5d048..00000000 --- a/services/content-watcher/apps/api/src/development.controller.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* -This is a controller providing some endpoints useful for development and testing. -*/ - -// eslint-disable-next-line max-classes-per-file -import { Controller, Get, Logger, NotFoundException, Param, Post } from '@nestjs/common'; -import { InjectQueue } from '@nestjs/bullmq'; -import { Queue } from 'bullmq'; -import { Job } from 'bullmq/dist/esm/classes/job'; -import { AnnouncementTypeDto, QueueConstants } from '../../../libs/common/src'; -import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; -import { AnnouncementType, createBroadcast, createProfile, createReaction, createReply, createTombstone, createUpdate } from '../../../libs/common/src/interfaces/dsnp'; -import { calculateDsnpHash } from '../../../libs/common/src/utils/ipfs'; - -@Controller('api/dev') -export class DevelopmentController { - private readonly logger: Logger; - - private readonly queueMapper: Map; - - constructor( - @InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue, - @InjectQueue(QueueConstants.BROADCAST_QUEUE_NAME) private broadcastQueue: Queue, - @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, - @InjectQueue(QueueConstants.REACTION_QUEUE_NAME) private reactionQueue: Queue, - @InjectQueue(QueueConstants.UPDATE_QUEUE_NAME) private updateQueue: Queue, - @InjectQueue(QueueConstants.PROFILE_QUEUE_NAME) private profileQueue: Queue, - @InjectQueue(QueueConstants.TOMBSTONE_QUEUE_NAME) private tombstoneQueue: Queue, - private ipfsService: IpfsService, - ) { - this.logger = new Logger(this.constructor.name); - this.queueMapper = new Map([ - [AnnouncementTypeDto.BROADCAST, broadcastQueue], - [AnnouncementTypeDto.REPLY, replyQueue], - [AnnouncementTypeDto.REACTION, reactionQueue], - [AnnouncementTypeDto.UPDATE, updateQueue], - [AnnouncementTypeDto.PROFILE, profileQueue], - [AnnouncementTypeDto.TOMBSTONE, tombstoneQueue], - ]); - } - - @Get('/request/:jobId') - async requestJob(@Param('jobId') jobId: string) { - this.logger.log(jobId); - const job = await this.requestQueue.getJob(jobId); - this.logger.log(job); - return job; - } - - @Get('/asset/:assetId') - // eslint-disable-next-line consistent-return - async getAsset(@Param('assetId') assetId: string) { - if (await this.ipfsService.isPinned(assetId)) { - return this.ipfsService.getPinned(assetId, false); - } - throw new NotFoundException(`${assetId} does not exist`); - } - - @Post('/dummy/announcement/:queueType/:count') - async populate(@Param('queueType') queueType: AnnouncementTypeDto, @Param('count') count: number) { - const promises: Promise[] = []; - // eslint-disable-next-line no-plusplus - for (let i = 0; i < count; i++) { - let data: any; - // eslint-disable-next-line default-case - const fromId = `${Math.floor(Math.random() * 100000000)}`; - const hash = `${Math.floor(Math.random() * 100000000)}`; - switch (queueType) { - case AnnouncementTypeDto.BROADCAST: - data = createBroadcast(fromId, `https://example.com/${Math.floor(Math.random() * 100000000)}`, hash); - break; - case AnnouncementTypeDto.PROFILE: - data = createProfile(fromId, `https://example.com/${Math.floor(Math.random() * 100000000)}`, hash); - break; - case AnnouncementTypeDto.UPDATE: - data = createUpdate(fromId, `https://example.com/${Math.floor(Math.random() * 100000000)}`, hash, AnnouncementType.Broadcast, `${Math.floor(Math.random() * 100000000)}`); - break; - case AnnouncementTypeDto.REPLY: - data = createReply( - fromId, - `https://example.com/${Math.floor(Math.random() * 100000000)}`, - hash, - `dsnp://0x${Math.floor(Math.random() * 100000000)}/0x${Math.floor(Math.random() * 100000000)}`, - ); - break; - case AnnouncementTypeDto.REACTION: - data = createReaction(fromId, '🤌🏼', `dsnp://0x${Math.floor(Math.random() * 100000000)}/0x${Math.floor(Math.random() * 100000000)}`, 1); - break; - case AnnouncementTypeDto.TOMBSTONE: - data = createTombstone(fromId, AnnouncementType.Reply, hash); - break; - default: - throw new Error('Announcement type not supported'); - } - // eslint-disable-next-line no-await-in-loop - const jobId = await calculateDsnpHash(Buffer.from(JSON.stringify(data))); - promises.push(this.queueMapper.get(queueType)!.add(`Dummy Job - ${data.id}`, data, { jobId, removeOnFail: false, removeOnComplete: true })); - } - await Promise.all(promises); - } -} From a4752d7f6ec0e2cd66b183aff264fb0e4010b3b3 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Wed, 11 Oct 2023 09:54:52 -0500 Subject: [PATCH 041/137] update metadata --- services/content-watcher/apps/api/src/metadata.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index d42ac984..8596c896 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -2,8 +2,7 @@ export default async () => { const t = { ["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"), - ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), - ["../../../libs/common/src/dtos/common.dto"]: await import("../../../libs/common/src/dtos/common.dto") + ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto") }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "assetUpload": { type: t["../../../libs/common/src/dtos/common.dto"].UploadResponseDto }, "broadcast": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reply": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "reaction": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "update": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "delete": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto }, "profile": { type: t["../../../libs/common/src/dtos/common.dto"].AnnouncementResponseDto } } }], [import("./development.controller"), { "DevelopmentController": { "requestJob": {}, "getAsset": { type: Object }, "populate": {} } }]] } }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {} } }]] } }; }; \ No newline at end of file From e3bcb7748fec2fa478d5d67fba63c1424bdae4de Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Fri, 13 Oct 2023 15:38:42 -0500 Subject: [PATCH 042/137] blockchain scanner service --- .../apps/api/src/api.module.ts | 39 +-------------- .../libs/common/src/scanner/scanner.module.ts | 50 +++++++++++++++++++ .../common/src/scanner/scanner.service.ts | 28 +++++++++++ .../libs/common/src/utils/queues.ts | 26 ++-------- 4 files changed, 83 insertions(+), 60 deletions(-) create mode 100644 services/content-watcher/libs/common/src/scanner/scanner.module.ts create mode 100644 services/content-watcher/libs/common/src/scanner/scanner.service.ts diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 64091590..d868d5b5 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -51,10 +51,7 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; }), BullModule.registerQueue( { - name: QueueConstants.ASSET_QUEUE_NAME, - }, - { - name: QueueConstants.REQUEST_QUEUE_NAME, + name: QueueConstants.IPFS_QUEUE, }, { name: QueueConstants.BROADCAST_QUEUE_NAME, @@ -74,18 +71,6 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; { name: QueueConstants.PROFILE_QUEUE_NAME, }, - { - name: QueueConstants.BATCH_QUEUE_NAME, - }, - { - name: QueueConstants.PUBLISH_QUEUE_NAME, - }, - { - name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, - }, - { - name: QueueConstants.STATUS_QUEUE_NAME, - }, ), // Bullboard UI @@ -94,11 +79,7 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; adapter: ExpressAdapter, }), BullBoardModule.forFeature({ - name: QueueConstants.ASSET_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.REQUEST_QUEUE_NAME, + name: QueueConstants.IPFS_QUEUE, adapter: BullMQAdapter, }), BullBoardModule.forFeature({ @@ -125,22 +106,6 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; name: QueueConstants.PROFILE_QUEUE_NAME, adapter: BullMQAdapter, }), - BullBoardModule.forFeature({ - name: QueueConstants.BATCH_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.PUBLISH_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.TRANSACTION_RECEIPT_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.STATUS_QUEUE_NAME, - adapter: BullMQAdapter, - }), EventEmitterModule.forRoot({ // Use this instance throughout the application global: true, diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts new file mode 100644 index 00000000..1edb3e82 --- /dev/null +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -0,0 +1,50 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bullmq'; +import { ScheduleModule } from '@nestjs/schedule'; +import { ConfigModule } from '../config/config.module'; +import { ScannerService } from './scanner.service'; +import { BlockchainModule } from '../blockchain/blockchain.module'; +import { ConfigService } from '../config/config.service'; +import { QueueConstants } from '../utils/queues'; + +@Module({ + imports: [ + ConfigModule, + BlockchainModule, + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue({ + name: QueueConstants.IPFS_QUEUE, + }), + ScheduleModule.forRoot(), + ], + controllers: [], + providers: [ScannerService], + exports: [ScannerService], +}) +export class ScannerModule {} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts new file mode 100644 index 00000000..cacafc44 --- /dev/null +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -0,0 +1,28 @@ +/* eslint-disable no-underscore-dangle */ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectQueue } from '@nestjs/bullmq'; +import Redis from 'ioredis'; +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { ConfigService } from '../config/config.service'; +import { BlockchainService } from '../blockchain/blockchain.service'; +import { QueueConstants } from '../utils/queues'; + +@Injectable() +export class ScannerService { + private readonly logger; + + constructor( + @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue, + @InjectRedis() private readonly cache: Redis, + private readonly configService: ConfigService, + private readonly blockchainService: BlockchainService, + ) { + this.logger = new Logger(ScannerService.name); + } + + async start() { + this.logger.debug('Starting scanner'); + const lastScannedBlockNumber = BigInt(await this.cache.get('lastScannedBlock') || 0n); + const currentFinalizedBlockNumber = await this.blockchainService.getLatestFinalizedBlockNumber(); + } +} diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index 270e6eed..70801cd7 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -2,32 +2,12 @@ import { AnnouncementTypeDto } from '../dtos/common.dto'; export namespace QueueConstants { /** - * Name of the queue that has all incoming asset uploads + * Name of the queue that has all incoming IPFS messages from the blockchain */ - export const ASSET_QUEUE_NAME = 'assetQueue'; - /** - * Name of the queue that has all incoming requests - */ - export const REQUEST_QUEUE_NAME = 'requestQueue'; - /** - * Name of the queue that has all individual announcements batched together - */ - export const BATCH_QUEUE_NAME = 'batchQueue'; - /** - * Name of the queue that has all items that needs to be published to Frequency chain - */ - export const PUBLISH_QUEUE_NAME = 'publishQueue'; - /** - * Name of the queue that has all the jobs and items that needs to run periodically or check their status - */ - export const STATUS_QUEUE_NAME = 'statusQueue'; + export const IPFS_QUEUE = 'ipfsQueue'; /** - * Name of the queue that has all the transaction receipts - */ - export const TRANSACTION_RECEIPT_QUEUE_NAME = 'transactionReceiptQueue'; - /** - * All of the announcement type queues + * Name of the queue that has all outgoing announcements from the blockchain */ export const BROADCAST_QUEUE_NAME = 'broadcastQueue'; export const REPLY_QUEUE_NAME = 'replyQueue'; From 381fbe29e197ed3ef315cbb16d4dd516943376d4 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 13:43:12 -0500 Subject: [PATCH 043/137] setup scanner and crawler service --- services/content-watcher/.env.docker.dev | 11 - services/content-watcher/INSTALLING.md | 2 - .../apps/api/src/api.controller.ts | 5 + .../apps/api/src/api.module.ts | 5 +- .../apps/api/src/api.service.ts | 29 +-- services/content-watcher/env.template | 10 - .../common/src/config/config.service.spec.ts | 118 ----------- .../libs/common/src/config/config.service.ts | 56 +---- .../libs/common/src/config/env.config.ts | 42 ---- .../libs/common/src/constants.ts | 12 ++ .../src/interfaces/chain.filter.interface.ts | 12 ++ .../src/interfaces/ipfs.job.interface.ts | 20 ++ .../src/interfaces/request-job.interface.ts | 10 +- .../common/src/scanner/scanner.service.ts | 191 +++++++++++++++++- services/content-watcher/package-lock.json | 9 + 15 files changed, 262 insertions(+), 270 deletions(-) create mode 100644 services/content-watcher/libs/common/src/interfaces/chain.filter.interface.ts create mode 100644 services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.docker.dev index e3d64590..244a4eec 100644 --- a/services/content-watcher/.env.docker.dev +++ b/services/content-watcher/.env.docker.dev @@ -9,22 +9,11 @@ IPFS_BASIC_AUTH_SECRET="" IPFS_GATEWAY_URL="http://kubo_ipfs:8080/ipfs/[CID]" FREQUENCY_URL=ws://frequency:9944 -PROVIDER_ID=1 REDIS_URL=redis://redis:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 -PROVIDER_ACCOUNT_SEED_PHRASE="//Ferdie" -WEBHOOK_FAILURE_THRESHOLD=3 HEALTH_CHECK_SUCCESS_THRESHOLD=10 -WEBHOOK_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRIES=4 -CAPACITY_LIMIT='{"type":"percentage", "value":80}' ENVIRONMENT="dev" API_PORT=3000 - -FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 -ASSET_EXPIRATION_INTERVAL_SECONDS=300 -BATCH_INTERVAL_SECONDS=12 -BATCH_MAX_COUNT=1000 -ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 diff --git a/services/content-watcher/INSTALLING.md b/services/content-watcher/INSTALLING.md index eacdd0a3..83f56dce 100644 --- a/services/content-watcher/INSTALLING.md +++ b/services/content-watcher/INSTALLING.md @@ -59,8 +59,6 @@ The following is a list of environment variables that may be set to control the |Variable|required?|Description|Default| |-|-|-|-| |`FREQUENCY_URL`|**yes**|Blockchain URL|_none_| -|`PROVIDER_ID`|**yes**|MSA ID of provider|_none_| -|`PROVIDER_ACCOUNT_SEED_PHRASE`|**yes**|Seed phrase for provider control keypair|_none_| |`REDIS_URL`|**yes**|URL used to connect to Redis instance|_none_
\*preset to the internal Redis URL in the standalone container| |`BLOCKCHAIN_SCAN_INTERVAL_MINUTES`|no|# of minutes to wait in between scans of the blockchain|180| |`QUEUE_HIGH_WATER`|no|# of pending graph scan queue entries to allow before pausing blockchain scanning until the next scan cycle|1000| diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 11c375c4..f954e3bb 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -33,4 +33,9 @@ export class ApiController { status: HttpStatus.OK, }; } + + @Post('updateLastScannedBlock') + updateLastScannedBlock(@Body() body: { blockNumber: number }) { + return this.apiService.setLastSeenBlockNumber(body.blockNumber); + } } diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index d868d5b5..09591d60 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -12,10 +12,13 @@ import { ApiService } from './api.service'; import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; import { ConfigModule } from '../../../libs/common/src/config/config.module'; import { ConfigService } from '../../../libs/common/src/config/config.service'; +import { ScannerModule } from '../../../libs/common/src/scanner/scanner.module'; +import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; @Module({ imports: [ ConfigModule, + ScannerModule, RedisModule.forRootAsync( { imports: [ConfigModule], @@ -126,7 +129,7 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; }), ScheduleModule.forRoot(), ], - providers: [ConfigService, ApiService, IpfsService], + providers: [ConfigService, ApiService, IpfsService, ScannerService], controllers: [ApiController], exports: [], }) diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 09ce871d..825dacaf 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -1,24 +1,12 @@ -import { InjectQueue } from '@nestjs/bullmq'; import { Injectable, Logger } from '@nestjs/common'; -import { Queue } from 'bullmq'; import { createHash } from 'crypto'; -import { BulkJobOptions } from 'bullmq/dist/esm/interfaces'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; import Redis from 'ioredis'; -import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util'; -import { - AnnouncementResponseDto, - AnnouncementTypeDto, - AssetIncludedRequestDto, - IRequestJob, - isImage, - QueueConstants, - RequestTypeDto, - UploadResponseDto, -} from '../../../libs/common/src'; -import { calculateIpfsCID } from '../../../libs/common/src/utils/ipfs'; -import { IAssetJob, IAssetMetadata } from '../../../libs/common/src/interfaces/asset-job.interface'; -import { RedisUtils } from '../../../libs/common/src/utils/redis'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Queue } from 'bullmq'; +import { IRequestJob } from '../../../libs/common/src'; +import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; +import { LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; @Injectable() export class ApiService { @@ -26,12 +14,15 @@ export class ApiService { constructor( @InjectRedis() private redis: Redis, - @InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue, - @InjectQueue(QueueConstants.ASSET_QUEUE_NAME) private assetQueue: Queue, + private readonly scannerService: ScannerService, ) { this.logger = new Logger(this.constructor.name); } + public setLastSeenBlockNumber(blockNumber: number) { + return this.redis.set(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, blockNumber.toString()); + } + // eslint-disable-next-line class-methods-use-this private calculateJobId(jobWithoutId: IRequestJob): string { const stringVal = JSON.stringify(jobWithoutId); diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index 4a1c95e7..bf80482a 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -9,25 +9,15 @@ IPFS_BASIC_AUTH_SECRET="" IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" FREQUENCY_URL=ws://0.0.0.0:9944 -PROVIDER_ID=1 REDIS_URL=redis://0.0.0.0:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 -PROVIDER_ACCOUNT_SEED_PHRASE="//Alice" -WEBHOOK_FAILURE_THRESHOLD=3 HEALTH_CHECK_SUCCESS_THRESHOLD=10 -WEBHOOK_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRIES=4 -CAPACITY_LIMIT='{"type":"percentage", "value":80}' API_PORT=3000 # should be dev for e2e tests. Options [dev, rococo, mainnet] ENVIRONMENT=dev -FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 -ASSET_EXPIRATION_INTERVAL_SECONDS=300 -BATCH_INTERVAL_SECONDS=12 -BATCH_MAX_COUNT=1000 -ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 \ No newline at end of file diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index b588ae34..2d4a1fd0 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -43,22 +43,12 @@ describe('ContentPublishingConfigService', () => { IPFS_GATEWAY_URL: undefined, IPFS_BASIC_AUTH_USER: undefined, IPFS_BASIC_AUTH_SECRET: undefined, - PROVIDER_ID: undefined, BLOCKCHAIN_SCAN_INTERVAL_MINUTES: undefined, QUEUE_HIGH_WATER: undefined, - PROVIDER_ACCOUNT_SEED_PHRASE: undefined, - WEBHOOK_FAILURE_THRESHOLD: undefined, HEALTH_CHECK_SUCCESS_THRESHOLD: undefined, - WEBHOOK_RETRY_INTERVAL_SECONDS: undefined, HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: undefined, HEALTH_CHECK_MAX_RETRIES: undefined, - CAPACITY_LIMIT: undefined, - FILE_UPLOAD_MAX_SIZE_IN_BYTES: undefined, API_PORT: undefined, - ASSET_EXPIRATION_INTERVAL_SECONDS: undefined, - BATCH_INTERVAL_SECONDS: undefined, - BATCH_MAX_COUNT: undefined, - ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: undefined, }; beforeAll(() => { @@ -93,17 +83,6 @@ describe('ContentPublishingConfigService', () => { await expect(setupConfigService({ FREQUENCY_URL: 'invalid url', ...env })).rejects.toBeDefined(); }); - it('missing provider id should fail', async () => { - const { PROVIDER_ID: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ ...env })).rejects.toBeDefined(); - }); - - it('invalid provider id should fail', async () => { - const { PROVIDER_ID: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ PROVIDER_ID: 'bad string', ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ PROVIDER_ID: '-1', ...env })).rejects.toBeDefined(); - }); - it('invalid scan interval should fail', async () => { const { BLOCKCHAIN_SCAN_INTERVAL_MINUTES: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_MINUTES: -1, ...env })).rejects.toBeDefined(); @@ -118,18 +97,6 @@ describe('ContentPublishingConfigService', () => { await expect(setupConfigService({ QUEUE_HIGH_WATER: 'foo', ...env })).rejects.toBeDefined(); }); - it('missing provider account seed phrase should fail', async () => { - const { PROVIDER_ACCOUNT_SEED_PHRASE: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ PROVIDER_ACCOUNT_SEED_PHRASE: undefined, ...env })).rejects.toBeDefined(); - }); - - it('invalid webhook failure threshold should fail', async () => { - const { WEBHOOK_FAILURE_THRESHOLD: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ WEBHOOK_FAILURE_THRESHOLD: -1, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ WEBHOOK_FAILURE_THRESHOLD: 0, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ WEBHOOK_FAILURE_THRESHOLD: 'foo', ...env })).rejects.toBeDefined(); - }); - it('invalid health check success threshold should fail', async () => { const { HEALTH_CHECK_SUCCESS_THRESHOLD: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ HEALTH_CHECK_SUCCESS_THRESHOLD: -1, ...env })).rejects.toBeDefined(); @@ -137,13 +104,6 @@ describe('ContentPublishingConfigService', () => { await expect(setupConfigService({ HEALTH_CHECK_SUCCESS_THRESHOLD: 'foo', ...env })).rejects.toBeDefined(); }); - it('invalid webhook retry interval should fail', async () => { - const { WEBHOOK_RETRY_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ WEBHOOK_RETRY_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ WEBHOOK_RETRY_INTERVAL_SECONDS: 0, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ WEBHOOK_RETRY_INTERVAL_SECONDS: 'foo', ...env })).rejects.toBeDefined(); - }); - it('invalid health check max retry interval should fail', async () => { const { HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); @@ -157,48 +117,10 @@ describe('ContentPublishingConfigService', () => { await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRIES: 'foo', ...env })).rejects.toBeDefined(); }); - it('missing capacity limits should fail', async () => { - const { CAPACITY_LIMIT: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ CAPACITY_LIMIT: undefined, ...env })).rejects.toBeDefined(); - }); - - it('invalid capacity limit should fail', async () => { - const { CAPACITY_LIMIT: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ CAPACITY_LIMIT: '{ "type": "bad type", "value": 0 }', ...env })).rejects.toBeDefined(); - await expect(async () => setupConfigService({ CAPACITY_LIMIT: '{ "type": "percentage", "value": -1 }', ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ CAPACITY_LIMIT: '{ "type": "percentage", "value": 101 }', ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ CAPACITY_LIMIT: '{ "type": "amount", "value": -1 }', ...env })).rejects.toBeDefined(); - }); - - it('invalid max file size should fail', async () => { - const { FILE_UPLOAD_MAX_SIZE_IN_BYTES: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ FILE_UPLOAD_MAX_SIZE_IN_BYTES: -1, ...env })).rejects.toBeDefined(); - }); - it('invalid api port should fail', async () => { const { API_PORT: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ API_PORT: -1, ...env })).rejects.toBeDefined(); }); - - it('invalid asset expiration interval should fail', async () => { - const { ASSET_EXPIRATION_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ ASSET_EXPIRATION_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); - }); - - it('invalid batch interval should fail', async () => { - const { BATCH_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ BATCH_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); - }); - - it('invalid batch max count should fail', async () => { - const { BATCH_MAX_COUNT: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ BATCH_MAX_COUNT: -1, ...env })).rejects.toBeDefined(); - }); - - it('invalid asset upload verification delay should fail', async () => { - const { ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: -1, ...env })).rejects.toBeDefined(); - }); }); describe('valid environment', () => { @@ -227,18 +149,10 @@ describe('ContentPublishingConfigService', () => { expect(contentPublishingConfigService.getQueueHighWater()).toStrictEqual(parseInt(ALL_ENV.QUEUE_HIGH_WATER as string, 10)); }); - it('should get webhook failure threshold', () => { - expect(contentPublishingConfigService.getWebhookFailureThreshold()).toStrictEqual(parseInt(ALL_ENV.WEBHOOK_FAILURE_THRESHOLD as string, 10)); - }); - it('should get health check success threshold', () => { expect(contentPublishingConfigService.getHealthCheckSuccessThreshold()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_SUCCESS_THRESHOLD as string, 10)); }); - it('should get webhook retry interval', () => { - expect(contentPublishingConfigService.getWebhookRetryIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.WEBHOOK_RETRY_INTERVAL_SECONDS as string, 10)); - }); - it('should get health check max retry interval', () => { expect(contentPublishingConfigService.getHealthCheckMaxRetryIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS as string, 10)); }); @@ -247,40 +161,8 @@ describe('ContentPublishingConfigService', () => { expect(contentPublishingConfigService.getHealthCheckMaxRetries()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRIES as string, 10)); }); - it('should get provider id', () => { - expect(contentPublishingConfigService.getProviderId()).toStrictEqual(ALL_ENV.PROVIDER_ID as string); - }); - - it('should get provider seed phrase', () => { - expect(contentPublishingConfigService.getProviderAccountSeedPhrase()).toStrictEqual(ALL_ENV.PROVIDER_ACCOUNT_SEED_PHRASE); - }); - - it('should get capacity limit', () => { - expect(contentPublishingConfigService.getCapacityLimit()).toStrictEqual(JSON.parse(ALL_ENV.CAPACITY_LIMIT!)); - }); - - it('should get file upload max size in bytes', () => { - expect(contentPublishingConfigService.getFileUploadMaxSizeInBytes()).toStrictEqual(parseInt(ALL_ENV.FILE_UPLOAD_MAX_SIZE_IN_BYTES as string, 10)); - }); - it('should get api port', () => { expect(contentPublishingConfigService.getApiPort()).toStrictEqual(parseInt(ALL_ENV.API_PORT as string, 10)); }); - - it('should get asset expiration interval in seconds', () => { - expect(contentPublishingConfigService.getAssetExpirationIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.ASSET_EXPIRATION_INTERVAL_SECONDS as string, 10)); - }); - - it('should get asset upload verification delay in seconds', () => { - expect(contentPublishingConfigService.getAssetUploadVerificationDelaySeconds()).toStrictEqual(parseInt(ALL_ENV.ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS as string, 10)); - }); - - it('should get batch interval in seconds', () => { - expect(contentPublishingConfigService.getBatchIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.BATCH_INTERVAL_SECONDS as string, 10)); - }); - - it('should get batch max count', () => { - expect(contentPublishingConfigService.getBatchMaxCount()).toStrictEqual(parseInt(ALL_ENV.BATCH_MAX_COUNT as string, 10)); - }); }); }); diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index c21661f5..efdc4a77 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -2,7 +2,7 @@ https://docs.nestjs.com/providers#services */ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { ConfigService as NestConfigService } from '@nestjs/config'; import { EnvironmentDto } from '..'; import { ICapacityLimit } from '../interfaces/capacity-limit.interface'; @@ -15,31 +15,21 @@ export interface ConfigEnvironmentVariables { IPFS_BASIC_AUTH_SECRET: string; REDIS_URL: URL; FREQUENCY_URL: URL; - PROVIDER_ID: string; BLOCKCHAIN_SCAN_INTERVAL_MINUTES: number; QUEUE_HIGH_WATER: number; - WEBHOOK_FAILURE_THRESHOLD: number; HEALTH_CHECK_SUCCESS_THRESHOLD: number; - WEBHOOK_RETRY_INTERVAL_SECONDS: number; HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: number; HEALTH_CHECK_MAX_RETRIES: number; - PROVIDER_ACCOUNT_SEED_PHRASE: string; - CAPACITY_LIMIT: ICapacityLimit; - FILE_UPLOAD_MAX_SIZE_IN_BYTES: number; API_PORT: number; - ASSET_EXPIRATION_INTERVAL_SECONDS: number; - BATCH_INTERVAL_SECONDS: number; - BATCH_MAX_COUNT: number; - ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: number; } /// Config service to get global app and provider-specific config values. @Injectable() export class ConfigService { - private capacityLimit: ICapacityLimit; + private logger: Logger; constructor(private nestConfigService: NestConfigService) { - this.capacityLimit = JSON.parse(nestConfigService.get('CAPACITY_LIMIT')!); + this.logger = new Logger(this.constructor.name); } public get environment(): EnvironmentDto { @@ -62,18 +52,10 @@ export class ConfigService { return this.nestConfigService.get('QUEUE_HIGH_WATER')!; } - public getWebhookFailureThreshold(): number { - return this.nestConfigService.get('WEBHOOK_FAILURE_THRESHOLD')!; - } - public getHealthCheckSuccessThreshold(): number { return this.nestConfigService.get('HEALTH_CHECK_SUCCESS_THRESHOLD')!; } - public getWebhookRetryIntervalSeconds(): number { - return this.nestConfigService.get('WEBHOOK_RETRY_INTERVAL_SECONDS')!; - } - public getHealthCheckMaxRetryIntervalSeconds(): number { return this.nestConfigService.get('HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS')!; } @@ -82,18 +64,6 @@ export class ConfigService { return this.nestConfigService.get('HEALTH_CHECK_MAX_RETRIES')!; } - public getProviderId(): string { - return this.nestConfigService.get('PROVIDER_ID')!; - } - - public getProviderAccountSeedPhrase(): string { - return this.nestConfigService.get('PROVIDER_ACCOUNT_SEED_PHRASE')!; - } - - public getCapacityLimit(): ICapacityLimit { - return this.capacityLimit; - } - public getIpfsEndpoint(): string { return this.nestConfigService.get('IPFS_ENDPOINT')!; } @@ -118,27 +88,7 @@ export class ConfigService { return gatewayUrl.replace('[CID]', cid); } - public getFileUploadMaxSizeInBytes(): number { - return this.nestConfigService.get('FILE_UPLOAD_MAX_SIZE_IN_BYTES')!; - } - public getApiPort(): number { return this.nestConfigService.get('API_PORT')!; } - - public getAssetExpirationIntervalSeconds(): number { - return this.nestConfigService.get('ASSET_EXPIRATION_INTERVAL_SECONDS')!; - } - - public getBatchIntervalSeconds(): number { - return this.nestConfigService.get('BATCH_INTERVAL_SECONDS')!; - } - - public getBatchMaxCount(): number { - return this.nestConfigService.get('BATCH_MAX_COUNT')!; - } - - public getAssetUploadVerificationDelaySeconds(): number { - return this.nestConfigService.get('ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS')!; - } } diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index df363de2..0fa7ce81 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -14,55 +14,13 @@ export const configModuleOptions: ConfigModuleOptions = { IPFS_BASIC_AUTH_SECRET: Joi.string().allow('').default(''), REDIS_URL: Joi.string().uri().required(), FREQUENCY_URL: Joi.string().uri().required(), - PROVIDER_ID: Joi.required().custom((value: string, helpers) => { - try { - const id = BigInt(value); - if (id < 0) { - throw new Error('Provider ID must be > 0'); - } - } catch (e) { - return helpers.error('any.invalid'); - } - return value; - }), BLOCKCHAIN_SCAN_INTERVAL_MINUTES: Joi.number() .min(1) .default(3 * 60), QUEUE_HIGH_WATER: Joi.number().min(100).default(1000), - PROVIDER_ACCOUNT_SEED_PHRASE: Joi.string().required(), - WEBHOOK_FAILURE_THRESHOLD: Joi.number().min(1).default(3), - WEBHOOK_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(10), HEALTH_CHECK_SUCCESS_THRESHOLD: Joi.number().min(1).default(10), HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(64), HEALTH_CHECK_MAX_RETRIES: Joi.number().min(0).default(20), - FILE_UPLOAD_MAX_SIZE_IN_BYTES: Joi.number().min(1).required(), API_PORT: Joi.number().min(0).default(3000), - ASSET_EXPIRATION_INTERVAL_SECONDS: Joi.number().min(1).required(), - BATCH_INTERVAL_SECONDS: Joi.number().min(1).required(), - BATCH_MAX_COUNT: Joi.number().min(1).required(), - ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS: Joi.number().min(0).required(), - CAPACITY_LIMIT: Joi.string() - .custom((value: string, helpers) => { - try { - const obj = JSON.parse(value); - const schema = Joi.object({ - type: Joi.string() - .required() - .pattern(/^(percentage|amount)$/), - value: Joi.alternatives() - .conditional('type', { is: 'percentage', then: Joi.number().min(0).max(100), otherwise: Joi.number().min(0) }) - .required(), - }); - const result = schema.validate(obj); - if (result.error) { - return helpers.error('any.invalid'); - } - } catch (e) { - return helpers.error('any.invalid'); - } - - return value; - }) - .required(), }), }; diff --git a/services/content-watcher/libs/common/src/constants.ts b/services/content-watcher/libs/common/src/constants.ts index 8ea7d791..334e2e06 100644 --- a/services/content-watcher/libs/common/src/constants.ts +++ b/services/content-watcher/libs/common/src/constants.ts @@ -6,3 +6,15 @@ export const SECONDS_PER_BLOCK = 12; * Name of timeout event used for in memory scheduler */ export const CAPACITY_EPOCH_TIMEOUT_NAME = 'capacity-epoch-timeout'; + +/** + * Last seen block number key for Redis + * @type {string} + */ +export const LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY: string = 'lastSeenBlockNumberScanner'; + +/** + * Filters and Events to watch key for Redis + * @type {string} + */ +export const EVENTS_TO_WATCH_KEY: string = 'eventsToWatch'; diff --git a/services/content-watcher/libs/common/src/interfaces/chain.filter.interface.ts b/services/content-watcher/libs/common/src/interfaces/chain.filter.interface.ts new file mode 100644 index 00000000..2d0434c3 --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/chain.filter.interface.ts @@ -0,0 +1,12 @@ +/** + * Interface for chain filter options + * @interface IChainWatchOptions + * @property {string[]} schemaIds - The schema ids for which content should be watched for + * @property {string[]} msa_ids - The msa ids for which content should be watched for + */ +export interface IChainWatchOptions { + // Specific schema ids to watch for + schemaIds: string[]; + // Specific msa ids to watch for + msa_ids: string[]; +} diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts new file mode 100644 index 00000000..06da8568 --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -0,0 +1,20 @@ +export interface IIPFSJob { + msaId: string; + providerId: string; + cid: string; + blockNumber: bigint; + index: number; +} + +export function createIPFSQueueJob(msaId: string, providerId: string, blockNumber: bigint, cid: string, index: number): { key: string; data: IIPFSJob } { + return { + key: `${msaId}:${providerId}:${blockNumber}:${index}`, + data: { + msaId, + providerId, + cid, + blockNumber, + index, + } as IIPFSJob, + }; +} diff --git a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts index b75b2b7d..6bc372b6 100644 --- a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts @@ -1,11 +1,7 @@ -import { RequestTypeDto } from '../dtos/announcement.dto'; -import { AnnouncementTypeDto } from '../dtos/common.dto'; +import { IChainWatchOptions } from './chain.filter.interface'; export interface IRequestJob { id: string; - announcementType: AnnouncementTypeDto; - dsnpUserId: string; - assetToMimeType?: Map; - content?: RequestTypeDto; - dependencyAttempt: number; + blocksToCrawl: string[]; + filters: IChainWatchOptions; } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index cacafc44..eff3a187 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -1,28 +1,205 @@ /* eslint-disable no-underscore-dangle */ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import Redis from 'ioredis'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; +import { u16, u32 } from '@polkadot/types'; +import { BlockPaginationRequest, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; +import { Queue } from 'bullmq'; +import { firstValueFrom } from 'rxjs'; +import { BlockNumber } from '@polkadot/types/interfaces'; +import { IEventLike } from '@polkadot/types/types'; import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; import { QueueConstants } from '../utils/queues'; +import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../constants'; +import { IChainWatchOptions } from '../interfaces/chain.filter.interface'; +import { IIPFSJob, createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; @Injectable() -export class ScannerService { - private readonly logger; +export class ScannerService implements OnApplicationBootstrap { + private readonly logger: Logger; + + private scanInProgress = false; constructor( - @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue, @InjectRedis() private readonly cache: Redis, + @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, private readonly configService: ConfigService, private readonly blockchainService: BlockchainService, + private schedulerRegistry: SchedulerRegistry, ) { this.logger = new Logger(ScannerService.name); } - async start() { + async onApplicationBootstrap() { + const interval = setInterval(() => this.scan(), this.configService.getBlockchainScanIntervalMinutes() * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND); + this.schedulerRegistry.addInterval('blockchainScan', interval); + const initialTimeout = setTimeout(() => this.scan(), 0); + this.schedulerRegistry.addTimeout('initialScan', initialTimeout); + } + + async scan() { this.logger.debug('Starting scanner'); - const lastScannedBlockNumber = BigInt(await this.cache.get('lastScannedBlock') || 0n); - const currentFinalizedBlockNumber = await this.blockchainService.getLatestFinalizedBlockNumber(); + try { + if (this.scanInProgress) { + this.logger.debug('Scan already in progress'); + return; + } + // Only scan blocks if queue is empty + let queueSize = await this.ipfsQueue.count(); + if (queueSize > 0) { + this.logger.log('Deferring next blockchain scan until queue is empty'); + return; + } + + const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); + const eventsToWatch: IChainWatchOptions = JSON.parse(chainWatchFilters ?? ''); + + this.scanInProgress = true; + let lastScannedBlock = await this.getLastSeenBlockNumber(); + + let latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); + + if (!latestBlockHash.some((byte) => byte !== 0)) { + this.logger.log('No new blocks to read; no scan performed.'); + this.scanInProgress = false; + } + + while (!latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { + // eslint-disable-next-line no-await-in-loop + const events = (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); + const eventsPromises: Promise[] = []; + eventsPromises.push( + events.forEach(async (event: IEventLike) => { + if (eventsToWatch.msa_ids.length > 0) { + if (this.blockchainService.api.events.messages.MessagesStored.is(event) || this.blockchainService.api.events.messages.MessagesUpdated.is(event)) { + const schemaId = event.data[0] as SchemaId; + const blockNumber = event.data[1] as BlockNumber; + const paginationRequest = { + from_block: blockNumber.toBigInt(), + from_index: 0, + page_size: 1000, + to_block: blockNumber.toBigInt() + 1n, + }; + const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); + const senderMsaId = messageResponse.msa_id.unwrap().toString(); + const providerMsaId = messageResponse.provider_msa_id.unwrap().toString(); + if (eventsToWatch.msa_ids.includes(senderMsaId) || eventsToWatch.msa_ids.includes(providerMsaId)) { + return true; + } + } + } else { + return true; + } + return false; + }), + ); + // eslint-disable-next-line no-await-in-loop + const filtereredPromises = await Promise.all(eventsPromises); + + if (filtereredPromises.length > 0) { + this.logger.log(`Found ${filtereredPromises.length} messages to process`); + } + + const jobs = filtereredPromises.map(async ({ event }) => { + const schemaId: u16 = event.data?.schemaId; + const blockNumber: u32 = event.data?.blockNumber; + const paginationRequest = { + from_block: blockNumber.toBigInt(), + from_index: 0, + page_size: 1000, + to_block: blockNumber.toBigInt() + 1n, + }; + // eslint-disable-next-line no-await-in-loop + const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); + if (messageResponse.cid.isNone) { + return; + } + + const ipfsQueueJob = createIPFSQueueJob( + messageResponse.msa_id.unwrap().toString(), + messageResponse.provider_msa_id.unwrap().toString(), + blockNumber.toBigInt(), + messageResponse.cid.unwrap().toString(), + messageResponse.index.toNumber(), + ); + // eslint-disable-next-line no-await-in-loop + await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); + }); + + // eslint-disable-next-line no-await-in-loop + await Promise.all(jobs); + + // eslint-disable-next-line no-await-in-loop + await this.saveProgress(lastScannedBlock); + + lastScannedBlock += 1n; + + // eslint-disable-next-line no-await-in-loop + latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); + // eslint-disable-next-line no-await-in-loop + queueSize = await this.ipfsQueue.count(); + } + } catch (err) { + this.logger.error(err); + } + } + + async crawlBlockListWithFilters(blockList: bigint[], filters: IChainWatchOptions): Promise { + this.logger.log(`Crawling block list with filters: ${JSON.stringify(filters)}`); + // wait for current scan to finish + // eslint-disable-next-line no-await-in-loop + while ((await this.ipfsQueue.count()) > 0 && this.scanInProgress) { + this.logger.log('Scan already in progress: waiting for IPFS Job queue to empty'); + } + + this.scanInProgress = true; + try { + const jobs = blockList.map(async (blockNumber) => { + const paginationRequest = { + from_block: blockNumber, + from_index: 0, + page_size: 1000, + to_block: blockNumber + 1n, + }; + // eslint-disable-next-line no-await-in-loop + const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(filters.schemaIds[0], paginationRequest)); + + if (messageResponse.cid.isNone) { + return; + } + + const ipfsQueueJob = createIPFSQueueJob( + messageResponse.msa_id.unwrap().toString(), + messageResponse.provider_msa_id.unwrap().toString(), + blockNumber, + messageResponse.cid.unwrap().toString(), + messageResponse.index.toNumber(), + ); + // eslint-disable-next-line no-await-in-loop + await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); + }); + + // eslint-disable-next-line no-await-in-loop + await Promise.all(jobs); + this.scanInProgress = false; + } catch (err) { + this.logger.error(err); + } + } + + public async getLastSeenBlockNumber(): Promise { + return BigInt(((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0).toString()); + } + + private async saveProgress(blockNumber: bigint): Promise { + await this.setLastSeenBlockNumber(blockNumber); + } + + private async setLastSeenBlockNumber(b: bigint): Promise { + await this.cache.set(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, b.toString()); } } diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 82262fd7..d3261474 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -10972,6 +10972,15 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "node_modules/redoc-cli/node_modules/@types/mkdirp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", + "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", + "extraneous": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/redoc-cli/node_modules/@types/node": { "version": "15.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", From 3c405571f7919874ccaddc7d6036cbd5d91da7b5 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 14:25:35 -0500 Subject: [PATCH 044/137] refactor and make scanner simple --- .../libs/common/src/config/config.service.ts | 1 - .../src/interfaces/asset-job.interface.ts | 12 - .../common/src/interfaces/batch.interface.ts | 5 - .../interfaces/capacity-limit.interface.ts | 4 - .../common/src/scanner/scanner.service.ts | 265 +++++++++--------- 5 files changed, 139 insertions(+), 148 deletions(-) delete mode 100644 services/content-watcher/libs/common/src/interfaces/asset-job.interface.ts delete mode 100644 services/content-watcher/libs/common/src/interfaces/batch.interface.ts delete mode 100644 services/content-watcher/libs/common/src/interfaces/capacity-limit.interface.ts diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index efdc4a77..db96eabf 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -5,7 +5,6 @@ https://docs.nestjs.com/providers#services import { Injectable, Logger } from '@nestjs/common'; import { ConfigService as NestConfigService } from '@nestjs/config'; import { EnvironmentDto } from '..'; -import { ICapacityLimit } from '../interfaces/capacity-limit.interface'; export interface ConfigEnvironmentVariables { ENVIRONMENT: EnvironmentDto; diff --git a/services/content-watcher/libs/common/src/interfaces/asset-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/asset-job.interface.ts deleted file mode 100644 index 3da2dc91..00000000 --- a/services/content-watcher/libs/common/src/interfaces/asset-job.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface IAssetJob { - ipfsCid: string; - mimeType: string; - contentLocation: string; - metadataLocation: string; -} - -export interface IAssetMetadata { - ipfsCid: string; - mimeType: string; - createdOn: number; -} diff --git a/services/content-watcher/libs/common/src/interfaces/batch.interface.ts b/services/content-watcher/libs/common/src/interfaces/batch.interface.ts deleted file mode 100644 index a4b1266a..00000000 --- a/services/content-watcher/libs/common/src/interfaces/batch.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface IBatchMetadata { - batchId: string; - startTimestamp: number; - rowCount: number; -} diff --git a/services/content-watcher/libs/common/src/interfaces/capacity-limit.interface.ts b/services/content-watcher/libs/common/src/interfaces/capacity-limit.interface.ts deleted file mode 100644 index d2b1d858..00000000 --- a/services/content-watcher/libs/common/src/interfaces/capacity-limit.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ICapacityLimit { - type: 'percentage' | 'amount'; - value: number; -} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index eff3a187..99dd2e32 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -6,7 +6,7 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; import { u16, u32 } from '@polkadot/types'; -import { BlockPaginationRequest, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; +import { SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Queue } from 'bullmq'; import { firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; @@ -16,7 +16,7 @@ import { BlockchainService } from '../blockchain/blockchain.service'; import { QueueConstants } from '../utils/queues'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../constants'; import { IChainWatchOptions } from '../interfaces/chain.filter.interface'; -import { IIPFSJob, createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; +import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; @Injectable() export class ScannerService implements OnApplicationBootstrap { @@ -35,78 +35,119 @@ export class ScannerService implements OnApplicationBootstrap { } async onApplicationBootstrap() { - const interval = setInterval(() => this.scan(), this.configService.getBlockchainScanIntervalMinutes() * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND); - this.schedulerRegistry.addInterval('blockchainScan', interval); + this.scheduleInitialScan(); + this.scheduleBlockchainScan(); + } + + private scheduleInitialScan() { const initialTimeout = setTimeout(() => this.scan(), 0); this.schedulerRegistry.addTimeout('initialScan', initialTimeout); } + private scheduleBlockchainScan() { + const scanInterval = this.configService.getBlockchainScanIntervalMinutes() * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; + + const interval = setInterval(() => this.scan(), scanInterval); + this.schedulerRegistry.addInterval('blockchainScan', interval); + } + async scan() { this.logger.debug('Starting scanner'); + + if (this.scanInProgress) { + this.logger.debug('Scan already in progress'); + return; + } + + let queueSize = await this.ipfsQueue.count(); + + if (queueSize > 0) { + this.logger.log('Deferring next blockchain scan until queue is empty'); + return; + } + + const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); + const eventsToWatch: IChainWatchOptions = JSON.parse(chainWatchFilters ?? ''); + + this.scanInProgress = true; + let lastScannedBlock = await this.getLastSeenBlockNumber(); + let latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); + + if (!latestBlockHash.some((byte) => byte !== 0)) { + this.logger.log('No new blocks to read; no scan performed.'); + this.scanInProgress = false; + return; + } + + while (!latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { + // eslint-disable-next-line no-await-in-loop + const events = await this.fetchEventsFromBlockchain(latestBlockHash); + // eslint-disable-next-line no-await-in-loop + const jobs = await this.processEvents(events, eventsToWatch); + // eslint-disable-next-line no-await-in-loop + await this.queueIPFSJobs(jobs); + // eslint-disable-next-line no-await-in-loop + await this.saveProgress(lastScannedBlock); + lastScannedBlock += 1n; + // eslint-disable-next-line no-await-in-loop + latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); + // eslint-disable-next-line no-await-in-loop + queueSize = await this.ipfsQueue.count(); + } + + this.scanInProgress = false; + } + + async crawlBlockListWithFilters(blockList: bigint[], filters: IChainWatchOptions): Promise { + this.logger.debug(`Crawling block list with filters: ${JSON.stringify(filters)}`); + + // eslint-disable-next-line no-await-in-loop + while ((await this.ipfsQueue.count()) > 0 && this.scanInProgress) { + this.logger.log('Scan already in progress: waiting for IPFS Job queue to empty'); + } + + this.scanInProgress = true; + try { - if (this.scanInProgress) { - this.logger.debug('Scan already in progress'); - return; - } - // Only scan blocks if queue is empty - let queueSize = await this.ipfsQueue.count(); - if (queueSize > 0) { - this.logger.log('Deferring next blockchain scan until queue is empty'); - return; - } + await this.processBlockList(blockList, filters); + this.scanInProgress = false; + } catch (err) { + this.logger.error(err); + } + } - const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); - const eventsToWatch: IChainWatchOptions = JSON.parse(chainWatchFilters ?? ''); + private async processBlockList(blockList: bigint[], filters: IChainWatchOptions) { + const promises: Promise[] = []; - this.scanInProgress = true; - let lastScannedBlock = await this.getLastSeenBlockNumber(); + blockList.forEach(async (blockNumber) => { + const latestBlockHash = await this.blockchainService.getBlockHash(blockNumber); - let latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); + // eslint-disable-next-line no-await-in-loop + const events = await this.fetchEventsFromBlockchain(latestBlockHash); + // eslint-disable-next-line no-await-in-loop + const filteredEvents = await this.processEvents(events, filters); + // eslint-disable-next-line no-await-in-loop + await this.queueIPFSJobs(filteredEvents); - if (!latestBlockHash.some((byte) => byte !== 0)) { - this.logger.log('No new blocks to read; no scan performed.'); - this.scanInProgress = false; - } + promises.push(Promise.resolve()); + }); - while (!latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { - // eslint-disable-next-line no-await-in-loop - const events = (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); - const eventsPromises: Promise[] = []; - eventsPromises.push( - events.forEach(async (event: IEventLike) => { - if (eventsToWatch.msa_ids.length > 0) { - if (this.blockchainService.api.events.messages.MessagesStored.is(event) || this.blockchainService.api.events.messages.MessagesUpdated.is(event)) { - const schemaId = event.data[0] as SchemaId; - const blockNumber = event.data[1] as BlockNumber; - const paginationRequest = { - from_block: blockNumber.toBigInt(), - from_index: 0, - page_size: 1000, - to_block: blockNumber.toBigInt() + 1n, - }; - const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); - const senderMsaId = messageResponse.msa_id.unwrap().toString(); - const providerMsaId = messageResponse.provider_msa_id.unwrap().toString(); - if (eventsToWatch.msa_ids.includes(senderMsaId) || eventsToWatch.msa_ids.includes(providerMsaId)) { - return true; - } - } - } else { - return true; - } - return false; - }), - ); - // eslint-disable-next-line no-await-in-loop - const filtereredPromises = await Promise.all(eventsPromises); + await Promise.all(promises); + } - if (filtereredPromises.length > 0) { - this.logger.log(`Found ${filtereredPromises.length} messages to process`); - } + private async fetchEventsFromBlockchain(latestBlockHash: any) { + return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); + } - const jobs = filtereredPromises.map(async ({ event }) => { - const schemaId: u16 = event.data?.schemaId; - const blockNumber: u32 = event.data?.blockNumber; + private async processEvents(events: IEventLike[], eventsToWatch: IChainWatchOptions) { + const eventsPromises = events.map(async (event: IEventLike) => { + if (eventsToWatch.msa_ids.length > 0 || eventsToWatch.schemaIds.length > 0) { + if (this.blockchainService.api.events.messages.MessagesStored.is(event) || this.blockchainService.api.events.messages.MessagesUpdated.is(event)) { + if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.data[0].toString())) { + return false; + } + const schemaId = event.data[0] as SchemaId; + const blockNumber = event.data[1] as BlockNumber; const paginationRequest = { from_block: blockNumber.toBigInt(), from_index: 0, @@ -115,83 +156,55 @@ export class ScannerService implements OnApplicationBootstrap { }; // eslint-disable-next-line no-await-in-loop const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); - if (messageResponse.cid.isNone) { - return; + const senderMsaId = messageResponse.msa_id.unwrap().toString(); + const providerMsaId = messageResponse.provider_msa_id.unwrap().toString(); + if (eventsToWatch.msa_ids.includes(senderMsaId) || eventsToWatch.msa_ids.includes(providerMsaId)) { + return true; } + } + } else { + return true; + } + return false; + }); - const ipfsQueueJob = createIPFSQueueJob( - messageResponse.msa_id.unwrap().toString(), - messageResponse.provider_msa_id.unwrap().toString(), - blockNumber.toBigInt(), - messageResponse.cid.unwrap().toString(), - messageResponse.index.toNumber(), - ); - // eslint-disable-next-line no-await-in-loop - await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); - }); - - // eslint-disable-next-line no-await-in-loop - await Promise.all(jobs); + return events.filter((_, index) => eventsPromises[index]); + } - // eslint-disable-next-line no-await-in-loop - await this.saveProgress(lastScannedBlock); + private async queueIPFSJobs(events) { + const jobs = events.map(async (event: { data: { schemaId: u16; blockNumber: u32 } }) => { + const schemaId: u16 = event.data?.schemaId; + const blockNumber: u32 = event.data?.blockNumber; + const paginationRequest = { + from_block: blockNumber.toBigInt(), + from_index: 0, + page_size: 1000, + to_block: blockNumber.toBigInt() + 1n, + }; - lastScannedBlock += 1n; + // eslint-disable-next-line no-await-in-loop + const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); - // eslint-disable-next-line no-await-in-loop - latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); - // eslint-disable-next-line no-await-in-loop - queueSize = await this.ipfsQueue.count(); + if (messageResponse.cid.isNone) { + return; } - } catch (err) { - this.logger.error(err); - } - } - - async crawlBlockListWithFilters(blockList: bigint[], filters: IChainWatchOptions): Promise { - this.logger.log(`Crawling block list with filters: ${JSON.stringify(filters)}`); - // wait for current scan to finish - // eslint-disable-next-line no-await-in-loop - while ((await this.ipfsQueue.count()) > 0 && this.scanInProgress) { - this.logger.log('Scan already in progress: waiting for IPFS Job queue to empty'); - } - - this.scanInProgress = true; - try { - const jobs = blockList.map(async (blockNumber) => { - const paginationRequest = { - from_block: blockNumber, - from_index: 0, - page_size: 1000, - to_block: blockNumber + 1n, - }; - // eslint-disable-next-line no-await-in-loop - const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(filters.schemaIds[0], paginationRequest)); - - if (messageResponse.cid.isNone) { - return; - } - const ipfsQueueJob = createIPFSQueueJob( - messageResponse.msa_id.unwrap().toString(), - messageResponse.provider_msa_id.unwrap().toString(), - blockNumber, - messageResponse.cid.unwrap().toString(), - messageResponse.index.toNumber(), - ); - // eslint-disable-next-line no-await-in-loop - await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); - }); + const ipfsQueueJob = createIPFSQueueJob( + messageResponse.msa_id.unwrap().toString(), + messageResponse.provider_msa_id.unwrap().toString(), + blockNumber.toBigInt(), + messageResponse.cid.unwrap().toString(), + messageResponse.index.toNumber(), + ); // eslint-disable-next-line no-await-in-loop - await Promise.all(jobs); - this.scanInProgress = false; - } catch (err) { - this.logger.error(err); - } + await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); + }); + // eslint-disable-next-line no-await-in-loop + await Promise.all(jobs); } - public async getLastSeenBlockNumber(): Promise { + private async getLastSeenBlockNumber(): Promise { return BigInt(((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0).toString()); } From 1a622b08c3377fea3aa72e3d9dbf84db065bed0c Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 14:28:16 -0500 Subject: [PATCH 045/137] renaming and refactor --- .../.github/workflows/build.yml | 4 ++-- .../common/src/config/config.service.spec.ts | 24 +++++++++---------- services/content-watcher/package.json | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/services/content-watcher/.github/workflows/build.yml b/services/content-watcher/.github/workflows/build.yml index 27fda258..434637e0 100644 --- a/services/content-watcher/.github/workflows/build.yml +++ b/services/content-watcher/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build And Test ContentPublishing Service +name: Build And Test ContentWatcher Service concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: true @@ -80,4 +80,4 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs \ No newline at end of file + publish_dir: ./docs diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index 2d4a1fd0..a0f8a88f 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -34,7 +34,7 @@ const setupConfigService = async (envObj: any): Promise => { return moduleRef.get(ConfigService); }; -describe('ContentPublishingConfigService', () => { +describe('ContentWatcherConfigService', () => { const ALL_ENV: { [key: string]: string | undefined } = { ENVIRONMENT: undefined, REDIS_URL: undefined, @@ -124,45 +124,45 @@ describe('ContentPublishingConfigService', () => { }); describe('valid environment', () => { - let contentPublishingConfigService: ConfigService; + let contentWatcherConfigService: ConfigService; beforeAll(async () => { - contentPublishingConfigService = await setupConfigService(ALL_ENV); + contentWatcherConfigService = await setupConfigService(ALL_ENV); }); it('should be defined', () => { - expect(contentPublishingConfigService).toBeDefined(); + expect(contentWatcherConfigService).toBeDefined(); }); it('should get redis url', () => { - expect(contentPublishingConfigService.redisUrl?.toString()).toStrictEqual(ALL_ENV.REDIS_URL?.toString()); + expect(contentWatcherConfigService.redisUrl?.toString()).toStrictEqual(ALL_ENV.REDIS_URL?.toString()); }); it('should get frequency url', () => { - expect(contentPublishingConfigService.frequencyUrl?.toString()).toStrictEqual(ALL_ENV.FREQUENCY_URL?.toString()); + expect(contentWatcherConfigService.frequencyUrl?.toString()).toStrictEqual(ALL_ENV.FREQUENCY_URL?.toString()); }); it('should get scan interval', () => { - expect(contentPublishingConfigService.getBlockchainScanIntervalMinutes()).toStrictEqual(parseInt(ALL_ENV.BLOCKCHAIN_SCAN_INTERVAL_MINUTES as string, 10)); + expect(contentWatcherConfigService.getBlockchainScanIntervalMinutes()).toStrictEqual(parseInt(ALL_ENV.BLOCKCHAIN_SCAN_INTERVAL_MINUTES as string, 10)); }); it('should get queue high water mark', () => { - expect(contentPublishingConfigService.getQueueHighWater()).toStrictEqual(parseInt(ALL_ENV.QUEUE_HIGH_WATER as string, 10)); + expect(contentWatcherConfigService.getQueueHighWater()).toStrictEqual(parseInt(ALL_ENV.QUEUE_HIGH_WATER as string, 10)); }); it('should get health check success threshold', () => { - expect(contentPublishingConfigService.getHealthCheckSuccessThreshold()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_SUCCESS_THRESHOLD as string, 10)); + expect(contentWatcherConfigService.getHealthCheckSuccessThreshold()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_SUCCESS_THRESHOLD as string, 10)); }); it('should get health check max retry interval', () => { - expect(contentPublishingConfigService.getHealthCheckMaxRetryIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS as string, 10)); + expect(contentWatcherConfigService.getHealthCheckMaxRetryIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS as string, 10)); }); it('should get health check max retries', () => { - expect(contentPublishingConfigService.getHealthCheckMaxRetries()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRIES as string, 10)); + expect(contentWatcherConfigService.getHealthCheckMaxRetries()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRIES as string, 10)); }); it('should get api port', () => { - expect(contentPublishingConfigService.getApiPort()).toStrictEqual(parseInt(ALL_ENV.API_PORT as string, 10)); + expect(contentWatcherConfigService.getApiPort()).toStrictEqual(parseInt(ALL_ENV.API_PORT as string, 10)); }); }); }); diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index d616055c..4613fd32 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -141,7 +141,7 @@ "/libs/" ], "moduleNameMapper": { - "^@content-publishing-common(|/.*)$": "/libs/common/src/$1" + "^@content-watcher-common(|/.*)$": "/libs/common/src/$1" } } } From 2173e2979d6f5425a570e0100438f5841d261a41 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:33:01 +0000 Subject: [PATCH 046/137] Bump @babel/traverse Bumps and [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse). These dependencies needed to be updated together. Updates `@babel/traverse` from 7.22.5 to 7.23.2 - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) Updates `@babel/traverse` from 7.14.2 to 7.23.2 - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] --- services/content-watcher/package-lock.json | 189 ++++++++++++++------- 1 file changed, 132 insertions(+), 57 deletions(-) diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index d3261474..0dcfd724 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -230,15 +230,81 @@ "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "license": "MIT", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/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==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/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==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/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==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/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==" + }, + "node_modules/@babel/code-frame/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==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/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==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.22.5", "license": "MIT", @@ -286,10 +352,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "license": "MIT", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -353,18 +420,20 @@ "license": "ISC" }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "license": "MIT", + "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==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "license": "MIT", + "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==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -425,8 +494,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "license": "MIT", + "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==", "dependencies": { "@babel/types": "^7.22.5" }, @@ -442,8 +512,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "license": "MIT", + "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==", "engines": { "node": ">=6.9.0" } @@ -468,11 +539,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "license": "MIT", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -481,7 +553,8 @@ }, "node_modules/@babel/highlight/node_modules/ansi-styles": { "version": "3.2.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dependencies": { "color-convert": "^1.9.0" }, @@ -491,7 +564,8 @@ }, "node_modules/@babel/highlight/node_modules/chalk": { "version": "2.4.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -503,32 +577,37 @@ }, "node_modules/@babel/highlight/node_modules/color-convert": { "version": "1.9.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dependencies": { "color-name": "1.1.3" } }, "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", - "license": "MIT" + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", - "license": "MIT", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "engines": { "node": ">=0.8.0" } }, "node_modules/@babel/highlight/node_modules/has-flag": { "version": "3.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "engines": { "node": ">=4" } }, "node_modules/@babel/highlight/node_modules/supports-color": { "version": "5.5.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dependencies": { "has-flag": "^3.0.0" }, @@ -537,8 +616,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "license": "MIT", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -707,29 +787,31 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "license": "MIT", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@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.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -745,11 +827,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "license": "MIT", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -8808,7 +8891,8 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -10972,15 +11056,6 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, - "node_modules/redoc-cli/node_modules/@types/mkdirp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", - "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", - "extraneous": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/redoc-cli/node_modules/@types/node": { "version": "15.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", From c3f0e41b815e5b99a99f3e801df143d9e21a8047 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 16:26:51 -0500 Subject: [PATCH 047/137] running scanner --- services/content-watcher/apps/api/src/api.module.ts | 2 ++ .../content-watcher/libs/common/src/scanner/scanner.module.ts | 3 ++- .../libs/common/src/scanner/scanner.service.ts | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 09591d60..a40c796d 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -14,10 +14,12 @@ import { ConfigModule } from '../../../libs/common/src/config/config.module'; import { ConfigService } from '../../../libs/common/src/config/config.service'; import { ScannerModule } from '../../../libs/common/src/scanner/scanner.module'; import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; +import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; @Module({ imports: [ ConfigModule, + BlockchainModule, ScannerModule, RedisModule.forRootAsync( { diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts index 1edb3e82..cf5f4b51 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.module.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -10,6 +10,7 @@ import { ScannerService } from './scanner.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { ConfigService } from '../config/config.service'; import { QueueConstants } from '../utils/queues'; +import { BlockchainService } from '../blockchain/blockchain.service'; @Module({ imports: [ @@ -44,7 +45,7 @@ import { QueueConstants } from '../utils/queues'; ScheduleModule.forRoot(), ], controllers: [], - providers: [ScannerService], + providers: [ConfigService, BlockchainService, ScannerService], exports: [ScannerService], }) export class ScannerModule {} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index 99dd2e32..f69349f9 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -25,10 +25,10 @@ export class ScannerService implements OnApplicationBootstrap { private scanInProgress = false; constructor( - @InjectRedis() private readonly cache: Redis, - @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, private readonly configService: ConfigService, private readonly blockchainService: BlockchainService, + @InjectRedis() private readonly cache: Redis, + @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, private schedulerRegistry: SchedulerRegistry, ) { this.logger = new Logger(ScannerService.name); From d59670082c5042f95c3752e8bb556882fef3f8bb Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 16:32:07 -0500 Subject: [PATCH 048/137] api to reset scanner to specific block --- services/content-watcher/apps/api/src/api.controller.ts | 6 +++--- services/content-watcher/apps/api/src/api.service.ts | 2 +- .../libs/common/src/scanner/scanner.service.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index f954e3bb..940536b2 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -34,8 +34,8 @@ export class ApiController { }; } - @Post('updateLastScannedBlock') - updateLastScannedBlock(@Body() body: { blockNumber: number }) { - return this.apiService.setLastSeenBlockNumber(body.blockNumber); + @Post('resetScanner') + resetScanner(@Body() body: { blockNumber?: bigint }) { + return this.apiService.setLastSeenBlockNumber(body.blockNumber ?? 0n); } } diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 825dacaf..c4124a24 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -19,7 +19,7 @@ export class ApiService { this.logger = new Logger(this.constructor.name); } - public setLastSeenBlockNumber(blockNumber: number) { + public setLastSeenBlockNumber(blockNumber: bigint) { return this.redis.set(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, blockNumber.toString()); } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index f69349f9..952fe261 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -67,7 +67,7 @@ export class ScannerService implements OnApplicationBootstrap { } const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); - const eventsToWatch: IChainWatchOptions = JSON.parse(chainWatchFilters ?? ''); + const eventsToWatch: IChainWatchOptions = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; this.scanInProgress = true; let lastScannedBlock = await this.getLastSeenBlockNumber(); From fc2731c8392d319c82a3fbce37c152f13162e196 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 17:39:53 -0500 Subject: [PATCH 049/137] jump start --- .../apps/api/src/api.module.ts | 2 +- .../src/blockchain/blockchain.service.ts | 117 +----------------- .../libs/common/src/scanner/scanner.module.ts | 2 +- 3 files changed, 4 insertions(+), 117 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index a40c796d..6701157a 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -131,7 +131,7 @@ import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain }), ScheduleModule.forRoot(), ], - providers: [ConfigService, ApiService, IpfsService, ScannerService], + providers: [ApiService], controllers: [ApiController], exports: [], }) diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 5042be89..54269594 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -57,11 +57,11 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS } public getBlockHash(block: BlockNumber | AnyNumber): Promise { - return this.apiPromise.rpc.chain.getBlockHash(block); + return firstValueFrom(this.api.rpc.chain.getBlockHash(block)); } public getBlock(block: BlockHash): Promise { - return this.apiPromise.rpc.chain.getBlock(block); + return firstValueFrom(this.api.rpc.chain.getBlock(block)); } public async getLatestFinalizedBlockHash(): Promise { @@ -113,120 +113,7 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS return newApi.query[pallet][extrinsic](...args); } - public async capacityInfo(providerId: string): Promise<{ - providerId: string; - currentBlockNumber: number; - nextEpochStart: number; - remainingCapacity: bigint; - totalCapacityIssued: bigint; - currentEpoch: bigint; - }> { - const providerU64 = this.api.createType('u64', providerId); - const { epochStart }: PalletCapacityEpochInfo = await this.query('capacity', 'currentEpochInfo'); - const epochBlockLength: u32 = await this.query('capacity', 'epochLength'); - const capacityDetailsOption: Option = await this.query('capacity', 'capacityLedger', providerU64); - const { remainingCapacity, totalCapacityIssued } = capacityDetailsOption.unwrapOr({ remainingCapacity: 0, totalCapacityIssued: 0 }); - const currentBlock: u32 = await this.query('system', 'number'); - const currentEpoch = await this.getCurrentCapacityEpoch(); - return { - currentEpoch, - providerId, - currentBlockNumber: currentBlock.toNumber(), - nextEpochStart: epochStart.add(epochBlockLength).toNumber(), - remainingCapacity: typeof remainingCapacity === 'number' ? BigInt(remainingCapacity) : remainingCapacity.toBigInt(), - totalCapacityIssued: typeof totalCapacityIssued === 'number' ? BigInt(totalCapacityIssued) : totalCapacityIssued.toBigInt(), - }; - } - - public async getCurrentCapacityEpoch(): Promise { - const currentEpoch: u32 = await this.query('capacity', 'currentEpoch'); - return typeof currentEpoch === 'number' ? BigInt(currentEpoch) : currentEpoch.toBigInt(); - } - - public async getCurrentEpochLength(): Promise { - const epochLength: u32 = await this.query('capacity', 'epochLength'); - return typeof epochLength === 'number' ? epochLength : epochLength.toNumber(); - } - - public async capacityBatchLimit(): Promise { - return this.api.consts.frequencyTxPayment.maximumCapacityBatchLength.toNumber(); - } - - public async getSchema(schemaId: number): Promise { - const schema: PalletSchemasSchema = await this.query('schemas', 'schemas', schemaId); - return schema; - } - public async getNonce(account: Uint8Array): Promise { return this.rpc('system', 'accountNextIndex', account); } - - public async crawlBlockListForTx( - txHash: Hash, - blockList: bigint[], - successEvents: [{ pallet: string; event: string }], - ): Promise<{ found: boolean; success: boolean; blockHash?: BlockHash; capacityWithDrawn?: string; error?: RegistryError }> { - const txReceiptPromises: Promise<{ found: boolean; success: boolean; blockHash?: BlockHash; capacityWithDrawn?: string; error?: RegistryError }>[] = blockList.map( - async (blockNumber) => { - const blockHash = await this.getBlockHash(blockNumber); - const block = await this.getBlock(blockHash); - const txInfo = block.block.extrinsics.find((extrinsic) => extrinsic.hash.toString() === txHash.toString()); - - if (!txInfo) { - return { found: false, success: false }; - } - - this.logger.verbose(`Found tx ${txHash} in block ${blockNumber}`); - const at = await this.api.at(blockHash.toHex()); - const eventsPromise = firstValueFrom(at.query.system.events()); - - let isTxSuccess = false; - let totalBlockCapacity: bigint = 0n; - let txError: RegistryError | undefined; - - try { - const events = await eventsPromise; - - events.forEach((record) => { - const { event } = record; - const eventName = event.section; - const { method } = event; - const { data } = event; - this.logger.debug(`Received event: ${eventName} ${method} ${data}`); - - // find capacity withdrawn event - if (eventName.search('capacity') !== -1 && method.search('Withdrawn') !== -1) { - // allow lowercase constructor for eslint - // eslint-disable-next-line new-cap - const currentCapacity: u128 = new u128(this.api.registry, data[1]); - totalBlockCapacity += currentCapacity.toBigInt(); - } - - // check custom success events - if (successEvents.find((successEvent) => successEvent.pallet === eventName && successEvent.event === method)) { - this.logger.debug(`Found success event ${eventName} ${method}`); - isTxSuccess = true; - } - - // check for system extrinsic failure - if (eventName.search('system') !== -1 && method.search('ExtrinsicFailed') !== -1) { - const dispatchError = data[0] as DispatchError; - const moduleThatErrored = dispatchError.asModule; - const moduleError = dispatchError.registry.findMetaError(moduleThatErrored); - txError = moduleError; - this.logger.error(`Extrinsic failed with error: ${JSON.stringify(moduleError)}`); - } - }); - } catch (error) { - this.logger.error(error); - } - this.logger.debug(`Total capacity withdrawn in block: ${totalBlockCapacity.toString()}`); - return { found: true, success: isTxSuccess, blockHash, capacityWithDrawn: totalBlockCapacity.toString(), error: txError }; - }, - ); - const results = await Promise.all(txReceiptPromises); - const result = results.find((receipt) => receipt.found); - this.logger.debug(`Found tx receipt: ${JSON.stringify(result)}`); - return result ?? { found: false, success: false }; - } } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts index cf5f4b51..e0b8dee8 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.module.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -45,7 +45,7 @@ import { BlockchainService } from '../blockchain/blockchain.service'; ScheduleModule.forRoot(), ], controllers: [], - providers: [ConfigService, BlockchainService, ScannerService], + providers: [ScannerService], exports: [ScannerService], }) export class ScannerModule {} From 27090e29dfdc2aa39987bd5ef04d19dd2c4c7ba6 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 18:39:10 -0500 Subject: [PATCH 050/137] testing fixes --- .../libs/common/src/scanner/scanner.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index 952fe261..7616e373 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -71,8 +71,9 @@ export class ScannerService implements OnApplicationBootstrap { this.scanInProgress = true; let lastScannedBlock = await this.getLastSeenBlockNumber(); - let latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); - + const currentBlockNumber = lastScannedBlock + 1n; + let latestBlockHash = await this.blockchainService.getBlockHash(currentBlockNumber); + if (!latestBlockHash.some((byte) => byte !== 0)) { this.logger.log('No new blocks to read; no scan performed.'); this.scanInProgress = false; From 2ccd355bd0e52f6968e31f352ef08dbbb178f64f Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 18:42:41 -0500 Subject: [PATCH 051/137] update swagger --- services/content-watcher/swagger.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index 6ba043b8..a1593014 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -1,12 +1,19 @@ openapi: 3.0.0 paths: - "/api/health": + /api/health: get: operationId: ApiController_health parameters: [] responses: '200': description: '' + /api/resetScanner: + post: + operationId: ApiController_resetScanner + parameters: [] + responses: + '201': + description: '' info: title: Content Watcher Service API description: Content Watcher Service API @@ -25,4 +32,5 @@ components: type: apiKey in: cookie name: SESSION - schemas: + schemas: {} + responses: {} \ No newline at end of file From 96139b969c466596083d4aff197346edb1c0c136 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 20:12:33 -0500 Subject: [PATCH 052/137] event filtering works --- .../common/src/scanner/scanner.service.ts | 120 ++++++++++-------- 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index 7616e373..990aa74b 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -8,7 +8,7 @@ import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; import { u16, u32 } from '@polkadot/types'; import { SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Queue } from 'bullmq'; -import { firstValueFrom } from 'rxjs'; +import { async, firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; import { IEventLike } from '@polkadot/types/types'; import { ConfigService } from '../config/config.service'; @@ -52,51 +52,61 @@ export class ScannerService implements OnApplicationBootstrap { } async scan() { - this.logger.debug('Starting scanner'); + try { + this.logger.debug('Starting scanner'); - if (this.scanInProgress) { - this.logger.debug('Scan already in progress'); - return; - } + if (this.scanInProgress) { + this.logger.debug('Scan already in progress'); + return; + } - let queueSize = await this.ipfsQueue.count(); + let queueSize = await this.ipfsQueue.count(); - if (queueSize > 0) { - this.logger.log('Deferring next blockchain scan until queue is empty'); - return; - } + if (queueSize > 0) { + this.logger.log('Deferring next blockchain scan until queue is empty'); + return; + } - const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); - const eventsToWatch: IChainWatchOptions = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; + const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); + const eventsToWatch: IChainWatchOptions = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; - this.scanInProgress = true; - let lastScannedBlock = await this.getLastSeenBlockNumber(); - const currentBlockNumber = lastScannedBlock + 1n; - let latestBlockHash = await this.blockchainService.getBlockHash(currentBlockNumber); - - if (!latestBlockHash.some((byte) => byte !== 0)) { - this.logger.log('No new blocks to read; no scan performed.'); - this.scanInProgress = false; - return; - } + this.scanInProgress = true; + let lastScannedBlock = await this.getLastSeenBlockNumber(); + const currentBlockNumber = lastScannedBlock + 1n; + let latestBlockHash = await this.blockchainService.getBlockHash(currentBlockNumber); - while (!latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { - // eslint-disable-next-line no-await-in-loop - const events = await this.fetchEventsFromBlockchain(latestBlockHash); - // eslint-disable-next-line no-await-in-loop - const jobs = await this.processEvents(events, eventsToWatch); - // eslint-disable-next-line no-await-in-loop - await this.queueIPFSJobs(jobs); - // eslint-disable-next-line no-await-in-loop - await this.saveProgress(lastScannedBlock); - lastScannedBlock += 1n; - // eslint-disable-next-line no-await-in-loop - latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); - // eslint-disable-next-line no-await-in-loop - queueSize = await this.ipfsQueue.count(); + if (!latestBlockHash.some((byte) => byte !== 0)) { + this.logger.log('No new blocks to read; no scan performed.'); + this.scanInProgress = false; + return; + } + this.logger.log(`Starting scan from block #${currentBlockNumber} (${latestBlockHash})`); + + while (!latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { + // eslint-disable-next-line no-await-in-loop + const events = await this.fetchEventsFromBlockchain(latestBlockHash); + // eslint-disable-next-line no-await-in-loop + const filteredEvents = await this.processEvents(events, eventsToWatch); + // eslint-disable-next-line no-await-in-loop + await this.queueIPFSJobs(filteredEvents); + // eslint-disable-next-line no-await-in-loop + await this.saveProgress(lastScannedBlock); + lastScannedBlock += 1n; + // eslint-disable-next-line no-await-in-loop + latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); + // eslint-disable-next-line no-await-in-loop + queueSize = await this.ipfsQueue.count(); + } + if (latestBlockHash.isEmpty) { + this.logger.log(`Scan reached end-of-chain at block ${lastScannedBlock - 1n}`); + } else if (queueSize > this.configService.getQueueHighWater()) { + this.logger.log('Queue soft limit reached; pausing scan until next iteration'); + } + } catch (err) { + this.logger.error(err); + } finally { + this.scanInProgress = false; } - - this.scanInProgress = false; } async crawlBlockListWithFilters(blockList: bigint[], filters: IChainWatchOptions): Promise { @@ -140,13 +150,14 @@ export class ScannerService implements OnApplicationBootstrap { return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); } - private async processEvents(events: IEventLike[], eventsToWatch: IChainWatchOptions) { - const eventsPromises = events.map(async (event: IEventLike) => { - if (eventsToWatch.msa_ids.length > 0 || eventsToWatch.schemaIds.length > 0) { - if (this.blockchainService.api.events.messages.MessagesStored.is(event) || this.blockchainService.api.events.messages.MessagesUpdated.is(event)) { + private async processEvents(events: any, eventsToWatch: IChainWatchOptions) { + const filteredEvents = await Promise.all( + events.map(async (event) => { + if (event.section === 'messages' && event.method === 'MessagesStored') { if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.data[0].toString())) { - return false; + return null; } + const schemaId = event.data[0] as SchemaId; const blockNumber = event.data[1] as BlockNumber; const paginationRequest = { @@ -155,25 +166,24 @@ export class ScannerService implements OnApplicationBootstrap { page_size: 1000, to_block: blockNumber.toBigInt() + 1n, }; - // eslint-disable-next-line no-await-in-loop - const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); + + const messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); const senderMsaId = messageResponse.msa_id.unwrap().toString(); const providerMsaId = messageResponse.provider_msa_id.unwrap().toString(); - if (eventsToWatch.msa_ids.includes(senderMsaId) || eventsToWatch.msa_ids.includes(providerMsaId)) { - return true; + + if (eventsToWatch.msa_ids.length === 0 || eventsToWatch.msa_ids.includes(senderMsaId) || eventsToWatch.msa_ids.includes(providerMsaId)) { + return event; } } - } else { - return true; - } - return false; - }); + return null; + }), + ); - return events.filter((_, index) => eventsPromises[index]); + return filteredEvents.filter((event) => event !== null); } private async queueIPFSJobs(events) { - const jobs = events.map(async (event: { data: { schemaId: u16; blockNumber: u32 } }) => { + const jobs = events.map(async (event) => { const schemaId: u16 = event.data?.schemaId; const blockNumber: u32 = event.data?.blockNumber; const paginationRequest = { From 31bcfc8125a4ef6918a9ce71278cf7aba0134c13 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 20:27:40 -0500 Subject: [PATCH 053/137] scanner working as expect with api --- .../content-watcher/apps/api/src/api.controller.ts | 12 +++++++----- services/content-watcher/apps/api/src/api.service.ts | 1 + .../libs/common/src/dtos/common.dto.ts | 5 +++++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 940536b2..d2902fc7 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -3,19 +3,17 @@ import { FilesInterceptor } from '@nestjs/platform-express'; import { ApiBody, ApiConsumes } from '@nestjs/swagger'; import { ApiService } from './api.service'; import { - AnnouncementResponseDto, AnnouncementTypeDto, AssetIncludedRequestDto, BroadcastDto, DSNP_VALID_MIME_TYPES, DsnpUserIdParam, - FilesUploadDto, ProfileDto, ReactionDto, ReplyDto, TombstoneDto, UpdateDto, - UploadResponseDto, + ResetScannerDto, } from '../../../libs/common/src'; @Controller('api') @@ -35,7 +33,11 @@ export class ApiController { } @Post('resetScanner') - resetScanner(@Body() body: { blockNumber?: bigint }) { - return this.apiService.setLastSeenBlockNumber(body.blockNumber ?? 0n); + @ApiBody({ + description: 'blockNumber', + type: ResetScannerDto, + }) + resetScanner(@Body() resetScannerDto: ResetScannerDto) { + return this.apiService.setLastSeenBlockNumber(BigInt(resetScannerDto.blockNumber?? 0n)); } } diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index c4124a24..9764f98a 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -20,6 +20,7 @@ export class ApiService { } public setLastSeenBlockNumber(blockNumber: bigint) { + this.logger.warn(`Setting last seen block number to ${blockNumber}`); return this.redis.set(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, blockNumber.toString()); } diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index ffba7386..07e45cd2 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -24,6 +24,11 @@ export class FilesUploadDto { files: any[]; } +export class ResetScannerDto { + @ApiProperty() + blockNumber?: bigint; +} + // eslint-disable-next-line no-shadow export enum AnnouncementTypeDto { BROADCAST = 'broadcast', From c205cc101cb3fd343f0facbabe4c1d3528b194da Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 20:35:40 -0500 Subject: [PATCH 054/137] api to update watch options: msa_ids, schemaid only --- .../content-watcher/apps/api/src/api.controller.ts | 12 +++++++++++- services/content-watcher/apps/api/src/api.service.ts | 10 +++++++++- .../chain.watch.dto.ts} | 3 ++- .../common/src/interfaces/request-job.interface.ts | 4 ++-- .../libs/common/src/scanner/scanner.service.ts | 10 +++++----- 5 files changed, 29 insertions(+), 10 deletions(-) rename services/content-watcher/libs/common/src/{interfaces/chain.filter.interface.ts => dtos/chain.watch.dto.ts} (90%) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index d2902fc7..8ee86d78 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -15,6 +15,7 @@ import { UpdateDto, ResetScannerDto, } from '../../../libs/common/src'; +import { IChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @Controller('api') export class ApiController { @@ -38,6 +39,15 @@ export class ApiController { type: ResetScannerDto, }) resetScanner(@Body() resetScannerDto: ResetScannerDto) { - return this.apiService.setLastSeenBlockNumber(BigInt(resetScannerDto.blockNumber?? 0n)); + return this.apiService.setLastSeenBlockNumber(BigInt(resetScannerDto.blockNumber ?? 0n)); + } + + @Post('setWatchOptions') + @ApiBody({ + description: 'watchOptions', + type: IChainWatchOptionsDto, + }) + setWatchOptions(@Body() watchOptions: IChainWatchOptionsDto) { + return this.apiService.setWatchOptions(watchOptions); } } diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 9764f98a..e2d59758 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -6,7 +6,8 @@ import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { IRequestJob } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; -import { LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; +import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; +import { IChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @Injectable() export class ApiService { @@ -24,6 +25,13 @@ export class ApiService { return this.redis.set(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, blockNumber.toString()); } + public async setWatchOptions(watchOptions: IChainWatchOptionsDto) { + this.logger.warn(`Setting watch options to ${JSON.stringify(watchOptions)}`); + const currentWatchOptions = await this.redis.get(EVENTS_TO_WATCH_KEY); + this.logger.warn(`Current watch options are ${currentWatchOptions}`); + await this.redis.set(EVENTS_TO_WATCH_KEY, JSON.stringify(watchOptions)); + } + // eslint-disable-next-line class-methods-use-this private calculateJobId(jobWithoutId: IRequestJob): string { const stringVal = JSON.stringify(jobWithoutId); diff --git a/services/content-watcher/libs/common/src/interfaces/chain.filter.interface.ts b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts similarity index 90% rename from services/content-watcher/libs/common/src/interfaces/chain.filter.interface.ts rename to services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts index 2d0434c3..e623bd84 100644 --- a/services/content-watcher/libs/common/src/interfaces/chain.filter.interface.ts +++ b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts @@ -4,9 +4,10 @@ * @property {string[]} schemaIds - The schema ids for which content should be watched for * @property {string[]} msa_ids - The msa ids for which content should be watched for */ -export interface IChainWatchOptions { +export class IChainWatchOptionsDto { // Specific schema ids to watch for schemaIds: string[]; + // Specific msa ids to watch for msa_ids: string[]; } diff --git a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts index 6bc372b6..001f104e 100644 --- a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts @@ -1,7 +1,7 @@ -import { IChainWatchOptions } from './chain.filter.interface'; +import { IChainWatchOptionsDto } from '../dtos/chain.watch.dto'; export interface IRequestJob { id: string; blocksToCrawl: string[]; - filters: IChainWatchOptions; + filters: IChainWatchOptionsDto; } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index 990aa74b..d8ebf88d 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -15,7 +15,7 @@ import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; import { QueueConstants } from '../utils/queues'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../constants'; -import { IChainWatchOptions } from '../interfaces/chain.filter.interface'; +import { IChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; @Injectable() @@ -68,7 +68,7 @@ export class ScannerService implements OnApplicationBootstrap { } const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); - const eventsToWatch: IChainWatchOptions = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; + const eventsToWatch: IChainWatchOptionsDto = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; this.scanInProgress = true; let lastScannedBlock = await this.getLastSeenBlockNumber(); @@ -109,7 +109,7 @@ export class ScannerService implements OnApplicationBootstrap { } } - async crawlBlockListWithFilters(blockList: bigint[], filters: IChainWatchOptions): Promise { + async crawlBlockListWithFilters(blockList: bigint[], filters: IChainWatchOptionsDto): Promise { this.logger.debug(`Crawling block list with filters: ${JSON.stringify(filters)}`); // eslint-disable-next-line no-await-in-loop @@ -127,7 +127,7 @@ export class ScannerService implements OnApplicationBootstrap { } } - private async processBlockList(blockList: bigint[], filters: IChainWatchOptions) { + private async processBlockList(blockList: bigint[], filters: IChainWatchOptionsDto) { const promises: Promise[] = []; blockList.forEach(async (blockNumber) => { @@ -150,7 +150,7 @@ export class ScannerService implements OnApplicationBootstrap { return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); } - private async processEvents(events: any, eventsToWatch: IChainWatchOptions) { + private async processEvents(events: any, eventsToWatch: IChainWatchOptionsDto) { const filteredEvents = await Promise.all( events.map(async (event) => { if (event.section === 'messages' && event.method === 'MessagesStored') { From 01219dfdc1f7146a691920493906f1b3bec2693e Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 20:46:31 -0500 Subject: [PATCH 055/137] setWatchOptions API --- .../apps/api/src/api.controller.ts | 2 +- .../libs/common/src/dtos/chain.watch.dto.ts | 22 +++++++++++++++++-- .../common/src/scanner/scanner.service.ts | 2 +- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 8ee86d78..97ef6bd9 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -44,7 +44,7 @@ export class ApiController { @Post('setWatchOptions') @ApiBody({ - description: 'watchOptions', + description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds', type: IChainWatchOptionsDto, }) setWatchOptions(@Body() watchOptions: IChainWatchOptionsDto) { diff --git a/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts index e623bd84..53eb4ca0 100644 --- a/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts @@ -1,3 +1,7 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsArray, IsOptional } from 'class-validator'; + /** * Interface for chain filter options * @interface IChainWatchOptions @@ -6,8 +10,22 @@ */ export class IChainWatchOptionsDto { // Specific schema ids to watch for + @IsOptional() + @IsArray() + @Type(() => String) + @ApiProperty({ + description: 'Specific schema ids to watch for', + example: ['1', '19'], + }) schemaIds: string[]; - // Specific msa ids to watch for - msa_ids: string[]; + // Specific dsnpIds (msa_id) to watch for + @IsOptional() + @IsArray() + @Type(() => String) + @ApiProperty({ + description: 'Specific dsnpIds (msa_id) to watch for', + example: ['10074', '100001'], + }) + dsnpIds: string[]; } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index d8ebf88d..42da45ae 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -171,7 +171,7 @@ export class ScannerService implements OnApplicationBootstrap { const senderMsaId = messageResponse.msa_id.unwrap().toString(); const providerMsaId = messageResponse.provider_msa_id.unwrap().toString(); - if (eventsToWatch.msa_ids.length === 0 || eventsToWatch.msa_ids.includes(senderMsaId) || eventsToWatch.msa_ids.includes(providerMsaId)) { + if (eventsToWatch.dsnpIds.length === 0 || eventsToWatch.dsnpIds.includes(senderMsaId) || eventsToWatch.dsnpIds.includes(providerMsaId)) { return event; } } From c2af5b1c26e698ec96090dfba25d2b7e43809169 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 20:54:25 -0500 Subject: [PATCH 056/137] pause/start scanner itself --- .../apps/api/src/api.controller.ts | 10 ++++++++++ .../content-watcher/apps/api/src/api.service.ts | 10 ++++++++++ .../libs/common/src/scanner/scanner.service.ts | 16 ++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 97ef6bd9..23fb3aa8 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -50,4 +50,14 @@ export class ApiController { setWatchOptions(@Body() watchOptions: IChainWatchOptionsDto) { return this.apiService.setWatchOptions(watchOptions); } + + @Post('pauseScanner') + pauseScanner() { + return this.apiService.pauseScanner(); + } + + @Post('startScanner') + startScanner() { + return this.apiService.resumeScanner(); + } } diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index e2d59758..21e74cff 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -32,6 +32,16 @@ export class ApiService { await this.redis.set(EVENTS_TO_WATCH_KEY, JSON.stringify(watchOptions)); } + public pauseScanner() { + this.logger.warn('Pausing scanner'); + return this.scannerService.pauseScanner(); + } + + public resumeScanner() { + this.logger.warn('Resuming scanner'); + return this.scannerService.resumeScanner(); + } + // eslint-disable-next-line class-methods-use-this private calculateJobId(jobWithoutId: IRequestJob): string { const stringVal = JSON.stringify(jobWithoutId); diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index 42da45ae..7d548bf5 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -24,6 +24,8 @@ export class ScannerService implements OnApplicationBootstrap { private scanInProgress = false; + private paused = false; + constructor( private readonly configService: ConfigService, private readonly blockchainService: BlockchainService, @@ -51,6 +53,16 @@ export class ScannerService implements OnApplicationBootstrap { this.schedulerRegistry.addInterval('blockchainScan', interval); } + public async pauseScanner() { + this.logger.debug('Pausing scanner'); + this.paused = true; + } + + public async resumeScanner() { + this.logger.debug('Resuming scanner'); + this.paused = false; + } + async scan() { try { this.logger.debug('Starting scanner'); @@ -60,6 +72,10 @@ export class ScannerService implements OnApplicationBootstrap { return; } + if (this.paused) { + this.logger.debug('Scanner is paused'); + return; + } let queueSize = await this.ipfsQueue.count(); if (queueSize > 0) { From b9b4d8ab34d1a2589d2d1b3027c2f28677053083 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 21:05:34 -0500 Subject: [PATCH 057/137] add search api as well --- .../apps/api/src/api.controller.ts | 10 +++++++ .../apps/api/src/api.service.ts | 8 ++++-- .../libs/common/src/dtos/request-job.dto.ts | 26 +++++++++++++++++++ .../content-watcher/libs/common/src/index.ts | 2 +- .../src/interfaces/request-job.interface.ts | 7 ----- 5 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 services/content-watcher/libs/common/src/dtos/request-job.dto.ts delete mode 100644 services/content-watcher/libs/common/src/interfaces/request-job.interface.ts diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 23fb3aa8..973acab1 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -14,6 +14,7 @@ import { TombstoneDto, UpdateDto, ResetScannerDto, + ContentSearchRequestDto, } from '../../../libs/common/src'; import { IChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @@ -60,4 +61,13 @@ export class ApiController { startScanner() { return this.apiService.resumeScanner(); } + + @Put('search') + @ApiBody({ + description: 'Search for DSNP content by id, startBlock, endBlock, and filters', + type: ContentSearchRequestDto, + }) + search(@Body() searchRequest: ContentSearchRequestDto) { + return this.apiService.searchContent(searchRequest); + } } diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 21e74cff..c98f31b4 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -4,7 +4,7 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; -import { IRequestJob } from '../../../libs/common/src'; +import { ContentSearchRequestDto } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; import { IChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @@ -42,8 +42,12 @@ export class ApiService { return this.scannerService.resumeScanner(); } + public async searchContent(contentSearchRequestDto: ContentSearchRequestDto) { + this.logger.debug(`Searching for content with request ${JSON.stringify(contentSearchRequestDto)}`); + } + // eslint-disable-next-line class-methods-use-this - private calculateJobId(jobWithoutId: IRequestJob): string { + private calculateJobId(jobWithoutId: ContentSearchRequestDto): string { const stringVal = JSON.stringify(jobWithoutId); return createHash('sha1').update(stringVal).digest('base64url'); } diff --git a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts new file mode 100644 index 00000000..bd81bb82 --- /dev/null +++ b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts @@ -0,0 +1,26 @@ +import { IsArray, IsOptional, IsString } from 'class-validator'; +import { IChainWatchOptionsDto } from './chain.watch.dto'; +import { ApiProperty } from '@nestjs/swagger'; + +export class ContentSearchRequestDto { + @IsOptional() + @IsString() + id: string; + + @IsString() + @ApiProperty({ + description: 'The starting block number to search from', + example: '100', + }) + startBlock: string; + + @IsString() + @ApiProperty({ + description: 'The ending block number to search to', + example: '101', + }) + endBlock: string; + + @IsOptional() + filters: IChainWatchOptionsDto; +} diff --git a/services/content-watcher/libs/common/src/index.ts b/services/content-watcher/libs/common/src/index.ts index d12f6422..9c415d2c 100644 --- a/services/content-watcher/libs/common/src/index.ts +++ b/services/content-watcher/libs/common/src/index.ts @@ -2,5 +2,5 @@ export * from './dtos/announcement.dto'; export * from './dtos/activity.dto'; export * from './dtos/common.dto'; export * from './dtos/validation.dto'; -export * from './interfaces/request-job.interface'; +export * from './dtos/request-job.dto'; export * from './utils/queues'; diff --git a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts b/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts deleted file mode 100644 index 001f104e..00000000 --- a/services/content-watcher/libs/common/src/interfaces/request-job.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { IChainWatchOptionsDto } from '../dtos/chain.watch.dto'; - -export interface IRequestJob { - id: string; - blocksToCrawl: string[]; - filters: IChainWatchOptionsDto; -} From dad26f86065f329f4d796d431bc37582680a4112 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 21:07:57 -0500 Subject: [PATCH 058/137] update swagger --- .../apps/api/src/api.service.ts | 2 +- .../libs/common/src/dtos/request-job.dto.ts | 2 +- services/content-watcher/swagger.yaml | 96 ++++++++++++++++++- 3 files changed, 96 insertions(+), 4 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index c98f31b4..1db853f6 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -45,7 +45,7 @@ export class ApiService { public async searchContent(contentSearchRequestDto: ContentSearchRequestDto) { this.logger.debug(`Searching for content with request ${JSON.stringify(contentSearchRequestDto)}`); } - + // eslint-disable-next-line class-methods-use-this private calculateJobId(jobWithoutId: ContentSearchRequestDto): string { const stringVal = JSON.stringify(jobWithoutId); diff --git a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts index bd81bb82..21876a1c 100644 --- a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts @@ -1,6 +1,6 @@ import { IsArray, IsOptional, IsString } from 'class-validator'; -import { IChainWatchOptionsDto } from './chain.watch.dto'; import { ApiProperty } from '@nestjs/swagger'; +import { IChainWatchOptionsDto } from './chain.watch.dto'; export class ContentSearchRequestDto { @IsOptional() diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index a1593014..d1fc5741 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -11,9 +11,58 @@ paths: post: operationId: ApiController_resetScanner parameters: [] + requestBody: + required: true + description: blockNumber + content: + application/json: + schema: + $ref: '#/components/schemas/ResetScannerDto' responses: '201': description: '' + /api/setWatchOptions: + post: + operationId: ApiController_setWatchOptions + parameters: [] + requestBody: + required: true + description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds' + content: + application/json: + schema: + $ref: '#/components/schemas/IChainWatchOptionsDto' + responses: + '201': + description: '' + /api/pauseScanner: + post: + operationId: ApiController_pauseScanner + parameters: [] + responses: + '201': + description: '' + /api/startScanner: + post: + operationId: ApiController_startScanner + parameters: [] + responses: + '201': + description: '' + /api/search: + put: + operationId: ApiController_search + parameters: [] + requestBody: + required: true + description: Search for DSNP content by id, startBlock, endBlock, and filters + content: + application/json: + schema: + $ref: '#/components/schemas/ContentSearchRequestDto' + responses: + '200': + description: '' info: title: Content Watcher Service API description: Content Watcher Service API @@ -32,5 +81,48 @@ components: type: apiKey in: cookie name: SESSION - schemas: {} - responses: {} \ No newline at end of file + schemas: + ResetScannerDto: + type: object + properties: + blockNumber: + format: int64 + type: integer + required: + - blockNumber + IChainWatchOptionsDto: + type: object + properties: + schemaIds: + description: Specific schema ids to watch for + example: + - '1' + - '19' + type: array + items: + type: string + dsnpIds: + description: Specific dsnpIds (msa_id) to watch for + example: + - '10074' + - '100001' + type: array + items: + type: string + required: + - schemaIds + - dsnpIds + ContentSearchRequestDto: + type: object + properties: + startBlock: + type: string + description: The starting block number to search from + example: '100' + endBlock: + type: string + description: The ending block number to search to + example: '101' + required: + - startBlock + - endBlock From 21b2a5ce8109214e9644005e3574bf486661bd23 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 16 Oct 2023 21:15:08 -0500 Subject: [PATCH 059/137] lint and publish swagger to gh pages --- .../content-watcher/libs/common/src/dtos/request-job.dto.ts | 3 +++ services/content-watcher/swagger.yaml | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts index 21876a1c..c3a7d096 100644 --- a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts @@ -22,5 +22,8 @@ export class ContentSearchRequestDto { endBlock: string; @IsOptional() + @ApiProperty({ + description: 'The schemaIds/dsnpIds to filter by', + }) filters: IChainWatchOptionsDto; } diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index d1fc5741..0264121a 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -123,6 +123,11 @@ components: type: string description: The ending block number to search to example: '101' + filters: + description: The schemaIds/dsnpIds to filter by + allOf: + - $ref: '#/components/schemas/IChainWatchOptionsDto' required: - startBlock - endBlock + - filters From 2721f62e38a911de2336a6e3a38a7da97db40857 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 10:03:00 -0500 Subject: [PATCH 060/137] add search api and request queues --- .../content-watcher/apps/api/src/api.controller.ts | 9 +++++++-- .../content-watcher/apps/api/src/api.module.ts | 7 +++++++ .../content-watcher/apps/api/src/api.service.ts | 14 +++++++++++++- .../libs/common/src/utils/queues.ts | 6 ++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 973acab1..88010cbe 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -67,7 +67,12 @@ export class ApiController { description: 'Search for DSNP content by id, startBlock, endBlock, and filters', type: ContentSearchRequestDto, }) - search(@Body() searchRequest: ContentSearchRequestDto) { - return this.apiService.searchContent(searchRequest); + async search(@Body() searchRequest: ContentSearchRequestDto) { + const jobResult = await this.apiService.searchContent(searchRequest); + + return { + status: HttpStatus.OK, + jobId: jobResult.id, + }; } } diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 6701157a..ca34253a 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -58,6 +58,9 @@ import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain { name: QueueConstants.IPFS_QUEUE, }, + { + name: QueueConstants.REQUEST_QUEUE_NAME, + }, { name: QueueConstants.BROADCAST_QUEUE_NAME, }, @@ -87,6 +90,10 @@ import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain name: QueueConstants.IPFS_QUEUE, adapter: BullMQAdapter, }), + BullBoardModule.forFeature({ + name: QueueConstants.REQUEST_QUEUE_NAME, + adapter: BullMQAdapter, + }), BullBoardModule.forFeature({ name: QueueConstants.BROADCAST_QUEUE_NAME, adapter: BullMQAdapter, diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 1db853f6..4bf74d74 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -4,7 +4,7 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; -import { ContentSearchRequestDto } from '../../../libs/common/src'; +import { ContentSearchRequestDto, QueueConstants } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; import { IChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @@ -15,6 +15,7 @@ export class ApiService { constructor( @InjectRedis() private redis: Redis, + @InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue, private readonly scannerService: ScannerService, ) { this.logger = new Logger(this.constructor.name); @@ -43,7 +44,18 @@ export class ApiService { } public async searchContent(contentSearchRequestDto: ContentSearchRequestDto) { + const jobId = contentSearchRequestDto.id ?? this.calculateJobId(contentSearchRequestDto); this.logger.debug(`Searching for content with request ${JSON.stringify(contentSearchRequestDto)}`); + + const job = await this.requestQueue.getJob(jobId); + if (job && !(await job.isCompleted())) { + this.logger.debug(`Found existing job ${jobId}`); + return job; + } + this.requestQueue.remove(jobId); + const jobPromise = this.requestQueue.add(`Content Search ${jobId}`, contentSearchRequestDto, { jobId }); + this.logger.debug(`Added job ${jobId}`); + return jobPromise; } // eslint-disable-next-line class-methods-use-this diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index 70801cd7..d5c972f2 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -6,6 +6,12 @@ export namespace QueueConstants { */ export const IPFS_QUEUE = 'ipfsQueue'; + /** + * Name of the queue that has all incoming requests for specific announcements + * from the blockchain + */ + export const REQUEST_QUEUE_NAME = 'requestQueue'; + /** * Name of the queue that has all outgoing announcements from the blockchain */ From e96095d32af2e047f55bc8d62790b365fe349a93 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 10:05:06 -0500 Subject: [PATCH 061/137] re-organize --- services/content-watcher/apps/api/src/api.module.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index ca34253a..71c5339a 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -56,10 +56,10 @@ import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain }), BullModule.registerQueue( { - name: QueueConstants.IPFS_QUEUE, + name: QueueConstants.REQUEST_QUEUE_NAME, }, { - name: QueueConstants.REQUEST_QUEUE_NAME, + name: QueueConstants.IPFS_QUEUE, }, { name: QueueConstants.BROADCAST_QUEUE_NAME, @@ -87,11 +87,11 @@ import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain adapter: ExpressAdapter, }), BullBoardModule.forFeature({ - name: QueueConstants.IPFS_QUEUE, + name: QueueConstants.REQUEST_QUEUE_NAME, adapter: BullMQAdapter, }), BullBoardModule.forFeature({ - name: QueueConstants.REQUEST_QUEUE_NAME, + name: QueueConstants.IPFS_QUEUE, adapter: BullMQAdapter, }), BullBoardModule.forFeature({ From 010ab32acd93f404176b690bf493f39a418f3bfb Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 10:10:51 -0500 Subject: [PATCH 062/137] cleanups and finalize apis --- .../apps/api/src/api.controller.ts | 21 +++++-------------- .../apps/api/src/api.module.ts | 2 -- .../apps/api/src/api.service.ts | 4 ++-- .../libs/common/src/dtos/chain.watch.dto.ts | 2 +- .../libs/common/src/dtos/request-job.dto.ts | 4 ++-- .../common/src/scanner/scanner.service.ts | 13 ++++++------ services/content-watcher/swagger.yaml | 6 +++--- 7 files changed, 19 insertions(+), 33 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 88010cbe..d09c48f6 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,22 +1,11 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Logger, Param, ParseFilePipeBuilder, Post, Put, UploadedFiles, UseInterceptors } from '@nestjs/common'; -import { FilesInterceptor } from '@nestjs/platform-express'; -import { ApiBody, ApiConsumes } from '@nestjs/swagger'; +import { Body, Controller, Get, HttpStatus, Logger, Post, Put } from '@nestjs/common'; +import { ApiBody } from '@nestjs/swagger'; import { ApiService } from './api.service'; import { - AnnouncementTypeDto, - AssetIncludedRequestDto, - BroadcastDto, - DSNP_VALID_MIME_TYPES, - DsnpUserIdParam, - ProfileDto, - ReactionDto, - ReplyDto, - TombstoneDto, - UpdateDto, ResetScannerDto, ContentSearchRequestDto, } from '../../../libs/common/src'; -import { IChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; +import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @Controller('api') export class ApiController { @@ -46,9 +35,9 @@ export class ApiController { @Post('setWatchOptions') @ApiBody({ description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds', - type: IChainWatchOptionsDto, + type: ChainWatchOptionsDto, }) - setWatchOptions(@Body() watchOptions: IChainWatchOptionsDto) { + setWatchOptions(@Body() watchOptions: ChainWatchOptionsDto) { return this.apiService.setWatchOptions(watchOptions); } diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 71c5339a..9ab7cc68 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -9,11 +9,9 @@ import { ExpressAdapter } from '@bull-board/express'; import { ApiController } from './api.controller'; import { QueueConstants } from '../../../libs/common/src'; import { ApiService } from './api.service'; -import { IpfsService } from '../../../libs/common/src/utils/ipfs.client'; import { ConfigModule } from '../../../libs/common/src/config/config.module'; import { ConfigService } from '../../../libs/common/src/config/config.service'; import { ScannerModule } from '../../../libs/common/src/scanner/scanner.module'; -import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; @Module({ diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 4bf74d74..fbb10034 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -7,7 +7,7 @@ import { Queue } from 'bullmq'; import { ContentSearchRequestDto, QueueConstants } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; -import { IChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; +import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @Injectable() export class ApiService { @@ -26,7 +26,7 @@ export class ApiService { return this.redis.set(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, blockNumber.toString()); } - public async setWatchOptions(watchOptions: IChainWatchOptionsDto) { + public async setWatchOptions(watchOptions: ChainWatchOptionsDto) { this.logger.warn(`Setting watch options to ${JSON.stringify(watchOptions)}`); const currentWatchOptions = await this.redis.get(EVENTS_TO_WATCH_KEY); this.logger.warn(`Current watch options are ${currentWatchOptions}`); diff --git a/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts index 53eb4ca0..b374faa5 100644 --- a/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts @@ -8,7 +8,7 @@ import { IsArray, IsOptional } from 'class-validator'; * @property {string[]} schemaIds - The schema ids for which content should be watched for * @property {string[]} msa_ids - The msa ids for which content should be watched for */ -export class IChainWatchOptionsDto { +export class ChainWatchOptionsDto { // Specific schema ids to watch for @IsOptional() @IsArray() diff --git a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts index c3a7d096..6cbb32d6 100644 --- a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts @@ -1,6 +1,6 @@ import { IsArray, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -import { IChainWatchOptionsDto } from './chain.watch.dto'; +import { ChainWatchOptionsDto } from './chain.watch.dto'; export class ContentSearchRequestDto { @IsOptional() @@ -25,5 +25,5 @@ export class ContentSearchRequestDto { @ApiProperty({ description: 'The schemaIds/dsnpIds to filter by', }) - filters: IChainWatchOptionsDto; + filters: ChainWatchOptionsDto; } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index 7d548bf5..c669fb4d 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -8,14 +8,13 @@ import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; import { u16, u32 } from '@polkadot/types'; import { SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Queue } from 'bullmq'; -import { async, firstValueFrom } from 'rxjs'; +import { firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; -import { IEventLike } from '@polkadot/types/types'; import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; import { QueueConstants } from '../utils/queues'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../constants'; -import { IChainWatchOptionsDto } from '../dtos/chain.watch.dto'; +import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; @Injectable() @@ -84,7 +83,7 @@ export class ScannerService implements OnApplicationBootstrap { } const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); - const eventsToWatch: IChainWatchOptionsDto = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; + const eventsToWatch: ChainWatchOptionsDto = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; this.scanInProgress = true; let lastScannedBlock = await this.getLastSeenBlockNumber(); @@ -125,7 +124,7 @@ export class ScannerService implements OnApplicationBootstrap { } } - async crawlBlockListWithFilters(blockList: bigint[], filters: IChainWatchOptionsDto): Promise { + async crawlBlockListWithFilters(blockList: bigint[], filters: ChainWatchOptionsDto): Promise { this.logger.debug(`Crawling block list with filters: ${JSON.stringify(filters)}`); // eslint-disable-next-line no-await-in-loop @@ -143,7 +142,7 @@ export class ScannerService implements OnApplicationBootstrap { } } - private async processBlockList(blockList: bigint[], filters: IChainWatchOptionsDto) { + private async processBlockList(blockList: bigint[], filters: ChainWatchOptionsDto) { const promises: Promise[] = []; blockList.forEach(async (blockNumber) => { @@ -166,7 +165,7 @@ export class ScannerService implements OnApplicationBootstrap { return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); } - private async processEvents(events: any, eventsToWatch: IChainWatchOptionsDto) { + private async processEvents(events: any, eventsToWatch: ChainWatchOptionsDto) { const filteredEvents = await Promise.all( events.map(async (event) => { if (event.section === 'messages' && event.method === 'MessagesStored') { diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index 0264121a..f7cd4060 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -31,7 +31,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/IChainWatchOptionsDto' + $ref: '#/components/schemas/ChainWatchOptionsDto' responses: '201': description: '' @@ -90,7 +90,7 @@ components: type: integer required: - blockNumber - IChainWatchOptionsDto: + ChainWatchOptionsDto: type: object properties: schemaIds: @@ -126,7 +126,7 @@ components: filters: description: The schemaIds/dsnpIds to filter by allOf: - - $ref: '#/components/schemas/IChainWatchOptionsDto' + - $ref: '#/components/schemas/ChainWatchOptionsDto' required: - startBlock - endBlock From 21ad2eb891157574a4eb1714db93cc577995a2d3 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 10:12:38 -0500 Subject: [PATCH 063/137] cleanup --- services/content-watcher/apps/api/src/api.controller.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index d09c48f6..0d698304 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,10 +1,7 @@ import { Body, Controller, Get, HttpStatus, Logger, Post, Put } from '@nestjs/common'; import { ApiBody } from '@nestjs/swagger'; import { ApiService } from './api.service'; -import { - ResetScannerDto, - ContentSearchRequestDto, -} from '../../../libs/common/src'; +import { ResetScannerDto, ContentSearchRequestDto } from '../../../libs/common/src'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @Controller('api') From dc0e2cbbd0f744429a54878ad19196007613b547 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 11:06:33 -0500 Subject: [PATCH 064/137] cleanup and separate crawler --- .../apps/api/src/api.service.ts | 10 -- .../libs/common/src/crawler/crawler.module.ts | 54 +++++++ .../common/src/crawler/crawler.service.ts | 136 ++++++++++++++++++ .../common/src/scanner/scanner.service.ts | 37 ----- .../libs/common/src/utils/base-consumer.ts | 58 ++++++++ 5 files changed, 248 insertions(+), 47 deletions(-) create mode 100644 services/content-watcher/libs/common/src/crawler/crawler.module.ts create mode 100644 services/content-watcher/libs/common/src/crawler/crawler.service.ts create mode 100644 services/content-watcher/libs/common/src/utils/base-consumer.ts diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index fbb10034..327eb9d0 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -63,14 +63,4 @@ export class ApiService { const stringVal = JSON.stringify(jobWithoutId); return createHash('sha1').update(stringVal).digest('base64url'); } - - private checkTransactionResult(result: [error: Error | null, result: unknown][] | null) { - this.logger.log(result); - for (let index = 0; result && index < result.length; index += 1) { - const [err, _id] = result[index]; - if (err) { - throw err; - } - } - } } diff --git a/services/content-watcher/libs/common/src/crawler/crawler.module.ts b/services/content-watcher/libs/common/src/crawler/crawler.module.ts new file mode 100644 index 00000000..965cf1a7 --- /dev/null +++ b/services/content-watcher/libs/common/src/crawler/crawler.module.ts @@ -0,0 +1,54 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { Module } from '@nestjs/common'; +import { BullModule } from '@nestjs/bullmq'; +import { ScheduleModule } from '@nestjs/schedule'; +import { ConfigModule } from '../config/config.module'; +import { CrawlerService } from './crawler.service'; +import { BlockchainModule } from '../blockchain/blockchain.module'; +import { ConfigService } from '../config/config.service'; +import { QueueConstants } from '../utils/queues'; +import { BlockchainService } from '../blockchain/blockchain.service'; + +@Module({ + imports: [ + ConfigModule, + BlockchainModule, + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue({ + name: QueueConstants.IPFS_QUEUE, + }), + BullModule.registerQueue({ + name: QueueConstants.REQUEST_QUEUE_NAME, + }), + ScheduleModule.forRoot(), + ], + controllers: [], + providers: [CrawlerService], + exports: [CrawlerService], +}) +export class CrawlerModule {} diff --git a/services/content-watcher/libs/common/src/crawler/crawler.service.ts b/services/content-watcher/libs/common/src/crawler/crawler.service.ts new file mode 100644 index 00000000..03073abc --- /dev/null +++ b/services/content-watcher/libs/common/src/crawler/crawler.service.ts @@ -0,0 +1,136 @@ +/* eslint-disable no-underscore-dangle */ +import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { InjectQueue, Processor, WorkerHost } from '@nestjs/bullmq'; +import Redis from 'ioredis'; +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; +import { u16, u32 } from '@polkadot/types'; +import { SchemaId } from '@frequency-chain/api-augment/interfaces'; +import { Job, Queue, Worker } from 'bullmq'; +import { firstValueFrom } from 'rxjs'; +import { BlockNumber } from '@polkadot/types/interfaces'; +import { ConfigService } from '../config/config.service'; +import { BlockchainService } from '../blockchain/blockchain.service'; +import { QueueConstants } from '../utils/queues'; +import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../constants'; +import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; +import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; +import { BaseConsumer } from '../utils/base-consumer'; +import { ContentSearchRequestDto } from '../dtos/request-job.dto'; + +@Injectable() +@Processor(QueueConstants.REQUEST_QUEUE_NAME, { + concurrency: 2, +}) +export class CrawlerService extends BaseConsumer { + constructor( + private readonly blockchainService: BlockchainService, + @InjectRedis() private readonly cache: Redis, + @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, + ) { + super(); + } + + async process(job: Job, token?: string | undefined): Promise { + this.logger.log(`Processing job ${job.id}`); + } + + async crawlBlockListWithFilters(blockList: bigint[], filters: ChainWatchOptionsDto): Promise { + this.logger.debug(`Crawling block list with filters: ${JSON.stringify(filters)}`); + + try { + await this.processBlockList(blockList, filters); + } catch (err) { + this.logger.error(err); + } + } + + private async processBlockList(blockList: bigint[], filters: ChainWatchOptionsDto) { + const promises: Promise[] = []; + + blockList.forEach(async (blockNumber) => { + const latestBlockHash = await this.blockchainService.getBlockHash(blockNumber); + + // eslint-disable-next-line no-await-in-loop + const events = await this.fetchEventsFromBlockchain(latestBlockHash); + // eslint-disable-next-line no-await-in-loop + const filteredEvents = await this.processEvents(events, filters); + // eslint-disable-next-line no-await-in-loop + await this.queueIPFSJobs(filteredEvents); + + promises.push(Promise.resolve()); + }); + + await Promise.all(promises); + } + + private async fetchEventsFromBlockchain(latestBlockHash: any) { + return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); + } + + private async processEvents(events: any, eventsToWatch: ChainWatchOptionsDto) { + const filteredEvents = await Promise.all( + events.map(async (event) => { + if (event.section === 'messages' && event.method === 'MessagesStored') { + if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.data[0].toString())) { + return null; + } + + const schemaId = event.data[0] as SchemaId; + const blockNumber = event.data[1] as BlockNumber; + const paginationRequest = { + from_block: blockNumber.toBigInt(), + from_index: 0, + page_size: 1000, + to_block: blockNumber.toBigInt() + 1n, + }; + + const messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + const senderMsaId = messageResponse.msa_id.unwrap().toString(); + const providerMsaId = messageResponse.provider_msa_id.unwrap().toString(); + + if (eventsToWatch.dsnpIds.length === 0 || eventsToWatch.dsnpIds.includes(senderMsaId) || eventsToWatch.dsnpIds.includes(providerMsaId)) { + return event; + } + } + return null; + }), + ); + + return filteredEvents.filter((event) => event !== null); + } + + private async queueIPFSJobs(events) { + const jobs = events.map(async (event) => { + const schemaId: u16 = event.data?.schemaId; + const blockNumber: u32 = event.data?.blockNumber; + const paginationRequest = { + from_block: blockNumber.toBigInt(), + from_index: 0, + page_size: 1000, + to_block: blockNumber.toBigInt() + 1n, + }; + + // eslint-disable-next-line no-await-in-loop + const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); + + if (messageResponse.cid.isNone) { + return; + } + + const ipfsQueueJob = createIPFSQueueJob( + messageResponse.msa_id.unwrap().toString(), + messageResponse.provider_msa_id.unwrap().toString(), + blockNumber.toBigInt(), + messageResponse.cid.unwrap().toString(), + messageResponse.index.toNumber(), + ); + + // eslint-disable-next-line no-await-in-loop + await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); + }); + // eslint-disable-next-line no-await-in-loop + await Promise.all(jobs); + } +} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index c669fb4d..fad0b0a6 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -124,43 +124,6 @@ export class ScannerService implements OnApplicationBootstrap { } } - async crawlBlockListWithFilters(blockList: bigint[], filters: ChainWatchOptionsDto): Promise { - this.logger.debug(`Crawling block list with filters: ${JSON.stringify(filters)}`); - - // eslint-disable-next-line no-await-in-loop - while ((await this.ipfsQueue.count()) > 0 && this.scanInProgress) { - this.logger.log('Scan already in progress: waiting for IPFS Job queue to empty'); - } - - this.scanInProgress = true; - - try { - await this.processBlockList(blockList, filters); - this.scanInProgress = false; - } catch (err) { - this.logger.error(err); - } - } - - private async processBlockList(blockList: bigint[], filters: ChainWatchOptionsDto) { - const promises: Promise[] = []; - - blockList.forEach(async (blockNumber) => { - const latestBlockHash = await this.blockchainService.getBlockHash(blockNumber); - - // eslint-disable-next-line no-await-in-loop - const events = await this.fetchEventsFromBlockchain(latestBlockHash); - // eslint-disable-next-line no-await-in-loop - const filteredEvents = await this.processEvents(events, filters); - // eslint-disable-next-line no-await-in-loop - await this.queueIPFSJobs(filteredEvents); - - promises.push(Promise.resolve()); - }); - - await Promise.all(promises); - } - private async fetchEventsFromBlockchain(latestBlockHash: any) { return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); } diff --git a/services/content-watcher/libs/common/src/utils/base-consumer.ts b/services/content-watcher/libs/common/src/utils/base-consumer.ts new file mode 100644 index 00000000..cc752f09 --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/base-consumer.ts @@ -0,0 +1,58 @@ +import { OnWorkerEvent, WorkerHost } from '@nestjs/bullmq'; +import { Logger, OnModuleDestroy } from '@nestjs/common'; +import { Job, Worker } from 'bullmq'; +import { ProcessingUtils } from './processing'; + +export abstract class BaseConsumer extends WorkerHost implements OnModuleDestroy { + protected logger: Logger; + + private actives: Set; + + protected constructor() { + super(); + this.logger = new Logger(this.constructor.name); + this.actives = new Set(); + } + + trackJob(jobId: string) { + this.actives.add(jobId); + } + + unTrackJob(jobId: string) { + this.actives.delete(jobId); + } + + async onModuleDestroy(): Promise { + await this.worker?.close(false); + let maxWaitMs = ProcessingUtils.MAX_WAIT_FOR_GRACE_FULL_SHUTDOWN_MS; + while (this.actives.size > 0 && maxWaitMs > 0) { + // eslint-disable-next-line no-await-in-loop + await ProcessingUtils.delay(ProcessingUtils.DELAY_TO_CHECK_FOR_SHUTDOWN_MS); + maxWaitMs -= ProcessingUtils.DELAY_TO_CHECK_FOR_SHUTDOWN_MS; + } + } + + /** + * any method overriding this method should call this method directly, so we are able to track the executing jobs + */ + @OnWorkerEvent('active') + onActive(job: Job) { + this.trackJob(job.id!); + } + + /** + * any method overriding this method should call this method directly, so we are able to track the executing jobs + */ + @OnWorkerEvent('completed') + onCompleted(job: Job) { + this.unTrackJob(job.id!); + } + + /** + * any method overriding this method should call this method directly, so we are able to track the executing jobs + */ + @OnWorkerEvent('failed') + onFailed(job: Job) { + this.unTrackJob(job.id!); + } +} From e9700f621bf528a5a4e100467d37b5652f6b6fd8 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 11:17:30 -0500 Subject: [PATCH 065/137] crawler module --- .../apps/api/src/api.module.ts | 2 ++ .../common/src/crawler/crawler.service.ts | 22 +++++++++---------- .../src/interfaces/ipfs.job.interface.ts | 3 ++- .../common/src/scanner/scanner.service.ts | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 9ab7cc68..29501e5e 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -13,12 +13,14 @@ import { ConfigModule } from '../../../libs/common/src/config/config.module'; import { ConfigService } from '../../../libs/common/src/config/config.service'; import { ScannerModule } from '../../../libs/common/src/scanner/scanner.module'; import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; +import { CrawlerModule } from '../../../libs/common/src/crawler/crawler.module'; @Module({ imports: [ ConfigModule, BlockchainModule, ScannerModule, + CrawlerModule, RedisModule.forRootAsync( { imports: [ConfigModule], diff --git a/services/content-watcher/libs/common/src/crawler/crawler.service.ts b/services/content-watcher/libs/common/src/crawler/crawler.service.ts index 03073abc..ac9c893b 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.service.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.service.ts @@ -34,19 +34,16 @@ export class CrawlerService extends BaseConsumer { async process(job: Job, token?: string | undefined): Promise { this.logger.log(`Processing job ${job.id}`); - } - - async crawlBlockListWithFilters(blockList: bigint[], filters: ChainWatchOptionsDto): Promise { - this.logger.debug(`Crawling block list with filters: ${JSON.stringify(filters)}`); - - try { - await this.processBlockList(blockList, filters); - } catch (err) { - this.logger.error(err); + const blockList: bigint[] = []; + for (let i = job.data.startBlock; i <= job.data.endBlock; i += 1) { + blockList.push(BigInt(i)); } + await this.processBlockList(job.data.id, blockList, job.data.filters); + + this.logger.log(`Finished processing job ${job.id}`); } - private async processBlockList(blockList: bigint[], filters: ChainWatchOptionsDto) { + private async processBlockList(id: string, blockList: bigint[], filters: ChainWatchOptionsDto) { const promises: Promise[] = []; blockList.forEach(async (blockNumber) => { @@ -57,7 +54,7 @@ export class CrawlerService extends BaseConsumer { // eslint-disable-next-line no-await-in-loop const filteredEvents = await this.processEvents(events, filters); // eslint-disable-next-line no-await-in-loop - await this.queueIPFSJobs(filteredEvents); + await this.queueIPFSJobs(id, filteredEvents); promises.push(Promise.resolve()); }); @@ -101,7 +98,7 @@ export class CrawlerService extends BaseConsumer { return filteredEvents.filter((event) => event !== null); } - private async queueIPFSJobs(events) { + private async queueIPFSJobs(id: string, events) { const jobs = events.map(async (event) => { const schemaId: u16 = event.data?.schemaId; const blockNumber: u32 = event.data?.blockNumber; @@ -125,6 +122,7 @@ export class CrawlerService extends BaseConsumer { blockNumber.toBigInt(), messageResponse.cid.unwrap().toString(), messageResponse.index.toNumber(), + id, ); // eslint-disable-next-line no-await-in-loop diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index 06da8568..f0db0c2e 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -4,9 +4,10 @@ export interface IIPFSJob { cid: string; blockNumber: bigint; index: number; + requestId?: string; } -export function createIPFSQueueJob(msaId: string, providerId: string, blockNumber: bigint, cid: string, index: number): { key: string; data: IIPFSJob } { +export function createIPFSQueueJob(msaId: string, providerId: string, blockNumber: bigint, cid: string, index: number, requestId: string): { key: string; data: IIPFSJob } { return { key: `${msaId}:${providerId}:${blockNumber}:${index}`, data: { diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index fad0b0a6..720e4ff8 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -184,6 +184,7 @@ export class ScannerService implements OnApplicationBootstrap { blockNumber.toBigInt(), messageResponse.cid.unwrap().toString(), messageResponse.index.toNumber(), + '', ); // eslint-disable-next-line no-await-in-loop From 8c83b045a8f98c2254f344f9640db324552ee1e8 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 11:34:30 -0500 Subject: [PATCH 066/137] lint and cleanup --- .../libs/common/src/crawler/crawler.module.ts | 1 - .../libs/common/src/crawler/crawler.service.ts | 10 +++------- .../libs/common/src/scanner/scanner.module.ts | 1 - 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/services/content-watcher/libs/common/src/crawler/crawler.module.ts b/services/content-watcher/libs/common/src/crawler/crawler.module.ts index 965cf1a7..a03f7855 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.module.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.module.ts @@ -10,7 +10,6 @@ import { CrawlerService } from './crawler.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { ConfigService } from '../config/config.service'; import { QueueConstants } from '../utils/queues'; -import { BlockchainService } from '../blockchain/blockchain.service'; @Module({ imports: [ diff --git a/services/content-watcher/libs/common/src/crawler/crawler.service.ts b/services/content-watcher/libs/common/src/crawler/crawler.service.ts index ac9c893b..4e0ee0f2 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.service.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.service.ts @@ -1,19 +1,15 @@ /* eslint-disable no-underscore-dangle */ -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; -import { InjectQueue, Processor, WorkerHost } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { InjectQueue, Processor } from '@nestjs/bullmq'; import Redis from 'ioredis'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { SchedulerRegistry } from '@nestjs/schedule'; -import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; import { u16, u32 } from '@polkadot/types'; import { SchemaId } from '@frequency-chain/api-augment/interfaces'; -import { Job, Queue, Worker } from 'bullmq'; +import { Job, Queue } from 'bullmq'; import { firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; -import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; import { QueueConstants } from '../utils/queues'; -import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../constants'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts index e0b8dee8..1edb3e82 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.module.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -10,7 +10,6 @@ import { ScannerService } from './scanner.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { ConfigService } from '../config/config.service'; import { QueueConstants } from '../utils/queues'; -import { BlockchainService } from '../blockchain/blockchain.service'; @Module({ imports: [ From 4ff1225afc36c95ff5cb9b5dd87fe88d7d8e37fe Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 11:36:24 -0500 Subject: [PATCH 067/137] refactor --- .../content-watcher/libs/common/src/crawler/crawler.module.ts | 2 +- .../libs/common/src/crawler/{crawler.service.ts => crawler.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename services/content-watcher/libs/common/src/crawler/{crawler.service.ts => crawler.ts} (100%) diff --git a/services/content-watcher/libs/common/src/crawler/crawler.module.ts b/services/content-watcher/libs/common/src/crawler/crawler.module.ts index a03f7855..38f98476 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.module.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.module.ts @@ -6,7 +6,7 @@ import { Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; import { ConfigModule } from '../config/config.module'; -import { CrawlerService } from './crawler.service'; +import { CrawlerService } from './crawler'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { ConfigService } from '../config/config.service'; import { QueueConstants } from '../utils/queues'; diff --git a/services/content-watcher/libs/common/src/crawler/crawler.service.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts similarity index 100% rename from services/content-watcher/libs/common/src/crawler/crawler.service.ts rename to services/content-watcher/libs/common/src/crawler/crawler.ts From bec2a1ab42f0c9672b18f802a490a43e3af009c7 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 11:46:47 -0500 Subject: [PATCH 068/137] add response type to swagger --- services/content-watcher/apps/api/src/api.controller.ts | 6 +++++- services/content-watcher/swagger.yaml | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 0d698304..28f124f0 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Get, HttpStatus, Logger, Post, Put } from '@nestjs/common'; -import { ApiBody } from '@nestjs/swagger'; +import { ApiBody, ApiOkResponse } from '@nestjs/swagger'; import { ApiService } from './api.service'; import { ResetScannerDto, ContentSearchRequestDto } from '../../../libs/common/src'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @@ -53,6 +53,10 @@ export class ApiController { description: 'Search for DSNP content by id, startBlock, endBlock, and filters', type: ContentSearchRequestDto, }) + @ApiOkResponse({ + description: 'Returns a jobId to be used to retrieve the results', + type: String, + }) async search(@Body() searchRequest: ContentSearchRequestDto) { const jobResult = await this.apiService.searchContent(searchRequest); diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index f7cd4060..cdf4e96b 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -62,7 +62,11 @@ paths: $ref: '#/components/schemas/ContentSearchRequestDto' responses: '200': - description: '' + description: Returns a jobId to be used to retrieve the results + content: + application/json: + schema: + type: string info: title: Content Watcher Service API description: Content Watcher Service API @@ -131,3 +135,4 @@ components: - startBlock - endBlock - filters + responses: {} From 92f2fa80d06e230eb803608a0ef00941b9ea26da Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 11:56:06 -0500 Subject: [PATCH 069/137] check for pause --- .../content-watcher/libs/common/src/scanner/scanner.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index 720e4ff8..8b72ddde 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -97,7 +97,7 @@ export class ScannerService implements OnApplicationBootstrap { } this.logger.log(`Starting scan from block #${currentBlockNumber} (${latestBlockHash})`); - while (!latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { + while (!this.paused &&!latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { // eslint-disable-next-line no-await-in-loop const events = await this.fetchEventsFromBlockchain(latestBlockHash); // eslint-disable-next-line no-await-in-loop From 14a082728a7392151d7ea8b5e1453b90a6c11c12 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 11:56:44 -0500 Subject: [PATCH 070/137] check for if paused --- .../content-watcher/libs/common/src/scanner/scanner.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts index 8b72ddde..81c9134e 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.service.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -97,7 +97,7 @@ export class ScannerService implements OnApplicationBootstrap { } this.logger.log(`Starting scan from block #${currentBlockNumber} (${latestBlockHash})`); - while (!this.paused &&!latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { + while (!this.paused && !latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { // eslint-disable-next-line no-await-in-loop const events = await this.fetchEventsFromBlockchain(latestBlockHash); // eslint-disable-next-line no-await-in-loop From ef77ff4b604ac4b1083881445a4c5c671dc85032 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 12:21:23 -0500 Subject: [PATCH 071/137] refactor --- services/content-watcher/apps/api/src/api.service.ts | 2 +- .../content-watcher/libs/common/src/scanner/scanner.module.ts | 2 +- .../libs/common/src/scanner/{scanner.service.ts => scanner.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename services/content-watcher/libs/common/src/scanner/{scanner.service.ts => scanner.ts} (100%) diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 327eb9d0..5ac4077b 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -5,7 +5,7 @@ import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { ContentSearchRequestDto, QueueConstants } from '../../../libs/common/src'; -import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; +import { ScannerService } from '../../../libs/common/src/scanner/scanner'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts index 1edb3e82..b1354bfb 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.module.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -6,7 +6,7 @@ import { Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; import { ConfigModule } from '../config/config.module'; -import { ScannerService } from './scanner.service'; +import { ScannerService } from './scanner'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { ConfigService } from '../config/config.service'; import { QueueConstants } from '../utils/queues'; diff --git a/services/content-watcher/libs/common/src/scanner/scanner.service.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts similarity index 100% rename from services/content-watcher/libs/common/src/scanner/scanner.service.ts rename to services/content-watcher/libs/common/src/scanner/scanner.ts From 8062879011806c559c989b01a379b99eda694a72 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 13:04:55 -0500 Subject: [PATCH 072/137] finalize crawler --- .../libs/common/src/crawler/crawler.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 4e0ee0f2..44d3cc5a 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -31,7 +31,9 @@ export class CrawlerService extends BaseConsumer { async process(job: Job, token?: string | undefined): Promise { this.logger.log(`Processing job ${job.id}`); const blockList: bigint[] = []; - for (let i = job.data.startBlock; i <= job.data.endBlock; i += 1) { + const blockStart = BigInt(job.data.startBlock); + const blockEnd = BigInt(job.data.endBlock); + for (let i = blockStart; i <= blockEnd; i += 1n) { blockList.push(BigInt(i)); } await this.processBlockList(job.data.id, blockList, job.data.filters); @@ -44,11 +46,19 @@ export class CrawlerService extends BaseConsumer { blockList.forEach(async (blockNumber) => { const latestBlockHash = await this.blockchainService.getBlockHash(blockNumber); - + if (latestBlockHash.isEmpty) { + return; + } // eslint-disable-next-line no-await-in-loop const events = await this.fetchEventsFromBlockchain(latestBlockHash); + this.logger.debug(`Processing ${events.length} events for block ${blockNumber}`); + // eslint-disable-next-line no-await-in-loop const filteredEvents = await this.processEvents(events, filters); + if (filteredEvents.length === 0) { + this.logger.debug(`No events found for block ${blockNumber}`); + return; + } // eslint-disable-next-line no-await-in-loop await this.queueIPFSJobs(id, filteredEvents); From 802e5cf606c5dbbc804947b96bd56b6f7b630c97 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 13:17:43 -0500 Subject: [PATCH 073/137] set a starting block for better bootstraping --- services/content-watcher/.env.docker.dev | 1 + services/content-watcher/INSTALLING.md | 1 + services/content-watcher/env.template | 1 + .../libs/common/src/config/config.service.spec.ts | 1 + .../content-watcher/libs/common/src/config/config.service.ts | 5 +++++ .../content-watcher/libs/common/src/config/env.config.ts | 1 + services/content-watcher/libs/common/src/scanner/scanner.ts | 2 ++ 7 files changed, 12 insertions(+) diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.docker.dev index 244a4eec..0ffd0ded 100644 --- a/services/content-watcher/.env.docker.dev +++ b/services/content-watcher/.env.docker.dev @@ -9,6 +9,7 @@ IPFS_BASIC_AUTH_SECRET="" IPFS_GATEWAY_URL="http://kubo_ipfs:8080/ipfs/[CID]" FREQUENCY_URL=ws://frequency:9944 +STARTING_BLOCK="0" REDIS_URL=redis://redis:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 diff --git a/services/content-watcher/INSTALLING.md b/services/content-watcher/INSTALLING.md index 83f56dce..0426f3f7 100644 --- a/services/content-watcher/INSTALLING.md +++ b/services/content-watcher/INSTALLING.md @@ -59,6 +59,7 @@ The following is a list of environment variables that may be set to control the |Variable|required?|Description|Default| |-|-|-|-| |`FREQUENCY_URL`|**yes**|Blockchain URL|_none_| +|`STARTING_BLOCK`|**maybe**|Starting block for scanner to scan from|_none_| |`REDIS_URL`|**yes**|URL used to connect to Redis instance|_none_
\*preset to the internal Redis URL in the standalone container| |`BLOCKCHAIN_SCAN_INTERVAL_MINUTES`|no|# of minutes to wait in between scans of the blockchain|180| |`QUEUE_HIGH_WATER`|no|# of pending graph scan queue entries to allow before pausing blockchain scanning until the next scan cycle|1000| diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index bf80482a..331273b8 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -9,6 +9,7 @@ IPFS_BASIC_AUTH_SECRET="" IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" FREQUENCY_URL=ws://0.0.0.0:9944 +STARTING_BLOCK=0 REDIS_URL=redis://0.0.0.0:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index a0f8a88f..e81c50bb 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -39,6 +39,7 @@ describe('ContentWatcherConfigService', () => { ENVIRONMENT: undefined, REDIS_URL: undefined, FREQUENCY_URL: undefined, + STARTING_BLOCK: undefined, IPFS_ENDPOINT: undefined, IPFS_GATEWAY_URL: undefined, IPFS_BASIC_AUTH_USER: undefined, diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index db96eabf..a1263a49 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -14,6 +14,7 @@ export interface ConfigEnvironmentVariables { IPFS_BASIC_AUTH_SECRET: string; REDIS_URL: URL; FREQUENCY_URL: URL; + STARTING_BLOCK: string; BLOCKCHAIN_SCAN_INTERVAL_MINUTES: number; QUEUE_HIGH_WATER: number; HEALTH_CHECK_SUCCESS_THRESHOLD: number; @@ -43,6 +44,10 @@ export class ConfigService { return this.nestConfigService.get('FREQUENCY_URL')!; } + public get startingBlock(): string { + return this.nestConfigService.get('STARTING_BLOCK')!; + } + public getBlockchainScanIntervalMinutes(): number { return this.nestConfigService.get('BLOCKCHAIN_SCAN_INTERVAL_MINUTES') ?? 1; } diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 0fa7ce81..8d87146e 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -14,6 +14,7 @@ export const configModuleOptions: ConfigModuleOptions = { IPFS_BASIC_AUTH_SECRET: Joi.string().allow('').default(''), REDIS_URL: Joi.string().uri().required(), FREQUENCY_URL: Joi.string().uri().required(), + STARTING_BLOCK: Joi.string().default('0'), BLOCKCHAIN_SCAN_INTERVAL_MINUTES: Joi.number() .min(1) .default(3 * 60), diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 81c9134e..6e4f686a 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -36,6 +36,8 @@ export class ScannerService implements OnApplicationBootstrap { } async onApplicationBootstrap() { + const startingBlock = BigInt(this.configService.startingBlock); + this.setLastSeenBlockNumber(startingBlock); this.scheduleInitialScan(); this.scheduleBlockchainScan(); } From 5c589e83286a8f732be8f2cf4b1d0ecd925f4f1f Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 13:20:51 -0500 Subject: [PATCH 074/137] trick --- services/content-watcher/libs/common/src/scanner/scanner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 6e4f686a..e2f4b923 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -36,7 +36,7 @@ export class ScannerService implements OnApplicationBootstrap { } async onApplicationBootstrap() { - const startingBlock = BigInt(this.configService.startingBlock); + const startingBlock = BigInt(this.configService.startingBlock)-1n; this.setLastSeenBlockNumber(startingBlock); this.scheduleInitialScan(); this.scheduleBlockchainScan(); From ec4be34d7b5d4327b07ed2ea9d1a24d2e49be1cb Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 13:56:30 -0500 Subject: [PATCH 075/137] cleanups --- .../apps/api/src/api.module.ts | 2 + .../libs/common/src/crawler/crawler.module.ts | 8 ++ .../libs/common/src/crawler/crawler.ts | 4 +- .../src/interfaces/ipfs.job.interface.ts | 1 + .../libs/common/src/ipfs/ipfs.dsnp.ts | 35 ++++++++ .../libs/common/src/ipfs/ipfs.module.ts | 89 +++++++++++++++++++ .../libs/common/src/scanner/scanner.ts | 2 +- 7 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts create mode 100644 services/content-watcher/libs/common/src/ipfs/ipfs.module.ts diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 29501e5e..584d837a 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -14,6 +14,7 @@ import { ConfigService } from '../../../libs/common/src/config/config.service'; import { ScannerModule } from '../../../libs/common/src/scanner/scanner.module'; import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; import { CrawlerModule } from '../../../libs/common/src/crawler/crawler.module'; +import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; @Module({ imports: [ @@ -21,6 +22,7 @@ import { CrawlerModule } from '../../../libs/common/src/crawler/crawler.module'; BlockchainModule, ScannerModule, CrawlerModule, + IPFSProcessorModule, RedisModule.forRootAsync( { imports: [ConfigModule], diff --git a/services/content-watcher/libs/common/src/crawler/crawler.module.ts b/services/content-watcher/libs/common/src/crawler/crawler.module.ts index 38f98476..86e00d09 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.module.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.module.ts @@ -43,6 +43,14 @@ import { QueueConstants } from '../utils/queues'; }), BullModule.registerQueue({ name: QueueConstants.REQUEST_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }), ScheduleModule.forRoot(), ], diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 44d3cc5a..c490fda5 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -28,8 +28,8 @@ export class CrawlerService extends BaseConsumer { super(); } - async process(job: Job, token?: string | undefined): Promise { - this.logger.log(`Processing job ${job.id}`); + async process(job: Job): Promise { + this.logger.log(`Processing crawler job ${job.id}`); const blockList: bigint[] = []; const blockStart = BigInt(job.data.startBlock); const blockEnd = BigInt(job.data.endBlock); diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index f0db0c2e..712dd8bf 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -16,6 +16,7 @@ export function createIPFSQueueJob(msaId: string, providerId: string, blockNumbe cid, blockNumber, index, + requestId, } as IIPFSJob, }; } diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts new file mode 100644 index 00000000..4517f521 --- /dev/null +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -0,0 +1,35 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Injectable, Logger } from '@nestjs/common'; +import { Job, Queue } from 'bullmq'; +import Redis from 'ioredis'; +import { InjectQueue, Processor, WorkerHost } from '@nestjs/bullmq'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { randomUUID } from 'crypto'; +import * as fs from 'fs'; +import { ConfigService } from '../config/config.service'; +import { Announcement } from '../interfaces/dsnp'; +import { RedisUtils } from '../utils/redis'; +import { DsnpSchemas } from '../utils/dsnp.schema'; +import { QueueConstants } from '..'; +import { IIPFSJob } from '../interfaces/ipfs.job.interface'; +import { BaseConsumer } from '../utils/base-consumer'; + +@Injectable() +@Processor(QueueConstants.IPFS_QUEUE, { + concurrency: 2, +}) +export class IPFSContentProcessor extends BaseConsumer { + public logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private schedulerRegistry: SchedulerRegistry, + private configService: ConfigService, + ) { + super(); + } + + async process(job: Job): Promise { + this.logger.log(`IPFS Processing job ${job.id}`); + } +} diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts new file mode 100644 index 00000000..ecde719e --- /dev/null +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts @@ -0,0 +1,89 @@ +/* +https://docs.nestjs.com/modules +*/ + +import { BullModule } from '@nestjs/bullmq'; +import { Module } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { ConfigModule } from '../config/config.module'; +import { ConfigService } from '../config/config.service'; +import { BlockchainModule } from '../blockchain/blockchain.module'; +import { QueueConstants } from '..'; +import { IPFSContentProcessor } from './ipfs.dsnp'; + +@Module({ + imports: [ + BlockchainModule, + ConfigModule, + EventEmitterModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue( + { + name: QueueConstants.IPFS_QUEUE, + defaultJobOptions: { + attempts: 1, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.BROADCAST_QUEUE_NAME, + }, + { + name: QueueConstants.REACTION_QUEUE_NAME, + }, + { + name: QueueConstants.REPLY_QUEUE_NAME, + }, + { + name: QueueConstants.PROFILE_QUEUE_NAME, + }, + { + name: QueueConstants.UPDATE_QUEUE_NAME, + }, + { + name: QueueConstants.TOMBSTONE_QUEUE_NAME, + }, + ), + ], + controllers: [], + providers: [IPFSContentProcessor], + exports: [BullModule, IPFSContentProcessor], +}) +export class IPFSProcessorModule {} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index e2f4b923..d2f3872d 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -36,7 +36,7 @@ export class ScannerService implements OnApplicationBootstrap { } async onApplicationBootstrap() { - const startingBlock = BigInt(this.configService.startingBlock)-1n; + const startingBlock = BigInt(this.configService.startingBlock) - 1n; this.setLastSeenBlockNumber(startingBlock); this.scheduleInitialScan(); this.scheduleBlockchainScan(); From a7812a364dc5e20f784fad920cac4e559de2f95e Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 14:26:49 -0500 Subject: [PATCH 076/137] get parquet buffer and init reader --- .../src/blockchain/blockchain.service.ts | 5 ++ .../libs/common/src/crawler/crawler.ts | 1 + .../src/interfaces/ipfs.job.interface.ts | 14 ++++- .../libs/common/src/ipfs/ipfs.dsnp.ts | 51 ++++++++++++++++--- .../libs/common/src/ipfs/ipfs.module.ts | 5 +- .../libs/common/src/scanner/scanner.ts | 1 + 6 files changed, 65 insertions(+), 12 deletions(-) diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 54269594..63270ad2 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -116,4 +116,9 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS public async getNonce(account: Uint8Array): Promise { return this.rpc('system', 'accountNextIndex', account); } + + public async getSchema(schemaId: number): Promise { + const schema: PalletSchemasSchema = await this.query('schemas', 'schemas', schemaId); + return schema; + } } diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index c490fda5..45851ee8 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -123,6 +123,7 @@ export class CrawlerService extends BaseConsumer { } const ipfsQueueJob = createIPFSQueueJob( + schemaId.toNumber(), messageResponse.msa_id.unwrap().toString(), messageResponse.provider_msa_id.unwrap().toString(), blockNumber.toBigInt(), diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index 712dd8bf..1bb9af7e 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -2,14 +2,23 @@ export interface IIPFSJob { msaId: string; providerId: string; cid: string; + schemaId: number; blockNumber: bigint; index: number; requestId?: string; } -export function createIPFSQueueJob(msaId: string, providerId: string, blockNumber: bigint, cid: string, index: number, requestId: string): { key: string; data: IIPFSJob } { +export function createIPFSQueueJob( + schemaId: number, + msaId: string, + providerId: string, + blockNumber: bigint, + cid: string, + index: number, + requestId: string, +): { key: string; data: IIPFSJob } { return { - key: `${msaId}:${providerId}:${blockNumber}:${index}`, + key: `${msaId}:${providerId}:${blockNumber}:${index}:${schemaId}`, data: { msaId, providerId, @@ -17,6 +26,7 @@ export function createIPFSQueueJob(msaId: string, providerId: string, blockNumbe blockNumber, index, requestId, + schemaId, } as IIPFSJob, }; } diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index 4517f521..c6a3a9db 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -1,18 +1,21 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Injectable, Logger } from '@nestjs/common'; -import { Job, Queue } from 'bullmq'; +import { Job } from 'bullmq'; import Redis from 'ioredis'; -import { InjectQueue, Processor, WorkerHost } from '@nestjs/bullmq'; +import { Processor } from '@nestjs/bullmq'; import { SchedulerRegistry } from '@nestjs/schedule'; -import { randomUUID } from 'crypto'; -import * as fs from 'fs'; +import { CID } from 'multiformats'; +import { hexToString } from '@polkadot/util'; +import { PalletSchemasSchema } from '@polkadot/types/lookup'; +import { fromFrequencySchema } from '@dsnp/frequency-schemas/parquet'; +import parquet from '@dsnp/parquetjs'; import { ConfigService } from '../config/config.service'; -import { Announcement } from '../interfaces/dsnp'; -import { RedisUtils } from '../utils/redis'; -import { DsnpSchemas } from '../utils/dsnp.schema'; import { QueueConstants } from '..'; import { IIPFSJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; +import { IpfsService } from '../utils/ipfs.client'; +import { BlockchainService } from '../blockchain/blockchain.service'; +import { RedisUtils } from '../utils/redis'; @Injectable() @Processor(QueueConstants.IPFS_QUEUE, { @@ -25,11 +28,43 @@ export class IPFSContentProcessor extends BaseConsumer { @InjectRedis() private redis: Redis, private schedulerRegistry: SchedulerRegistry, private configService: ConfigService, + private ipfsService: IpfsService, + private blockchainService: BlockchainService, ) { super(); } async process(job: Job): Promise { - this.logger.log(`IPFS Processing job ${job.id}`); + try { + this.logger.log(`IPFS Processing job ${job.id}`); + this.logger.debug(`IPFS CID: ${job.data.cid} for schemaId: ${job.data.schemaId}`); + const cid = CID.parse(job.data.cid); + + const ipfsHash = cid.toV0().toString(); + this.logger.debug(`IPFS Hash: ${ipfsHash}`); + + const contentBuffer = await this.ipfsService.getPinned(ipfsHash); + const schemaCacheKey = `schema:${job.data.schemaId}`; + let cachedSchema: string | null = await this.redis.get(schemaCacheKey); + if (!cachedSchema) { + const schemaResponse = await this.blockchainService.getSchema(job.data.schemaId); + cachedSchema = JSON.stringify(schemaResponse); + await this.redis.setex(schemaCacheKey, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, cachedSchema); + } + + const frequencySchema: PalletSchemasSchema = JSON.parse(cachedSchema); + const hexString: string = Buffer.from(frequencySchema.model).toString('utf8'); + const schema = JSON.parse(hexToString(hexString)); + if (!schema) { + throw new Error(`Unable to parse schema for schemaId ${job.data.schemaId}`); + } + + const reader = await parquet.ParquetReader.openBuffer(contentBuffer); + const cursor = reader.getCursor(); + const records = []; + } catch (e) { + this.logger.error(`IPFS Job ${job.id} failed with error: ${e}`); + throw e; + } } } diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts index ecde719e..62ad2f94 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts @@ -11,6 +11,7 @@ import { ConfigService } from '../config/config.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { QueueConstants } from '..'; import { IPFSContentProcessor } from './ipfs.dsnp'; +import { IpfsService } from '../utils/ipfs.client'; @Module({ imports: [ @@ -83,7 +84,7 @@ import { IPFSContentProcessor } from './ipfs.dsnp'; ), ], controllers: [], - providers: [IPFSContentProcessor], - exports: [BullModule, IPFSContentProcessor], + providers: [IPFSContentProcessor, IpfsService], + exports: [BullModule, IPFSContentProcessor, IpfsService], }) export class IPFSProcessorModule {} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index d2f3872d..d81a79b3 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -181,6 +181,7 @@ export class ScannerService implements OnApplicationBootstrap { } const ipfsQueueJob = createIPFSQueueJob( + schemaId.toNumber(), messageResponse.msa_id.unwrap().toString(), messageResponse.provider_msa_id.unwrap().toString(), blockNumber.toBigInt(), From cf0e379a16d59cd04fe8ede0a9dd3d98b1c8cb75 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 15:04:02 -0500 Subject: [PATCH 077/137] handle parquet --- .../apps/api/src/api.module.ts | 7 ----- .../libs/common/src/ipfs/ipfs.dsnp.ts | 26 ++++++++++++++++--- .../libs/common/src/ipfs/ipfs.module.ts | 5 +--- .../libs/common/src/utils/queues.ts | 3 --- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 584d837a..8dd923bf 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -75,9 +75,6 @@ import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; { name: QueueConstants.TOMBSTONE_QUEUE_NAME, }, - { - name: QueueConstants.UPDATE_QUEUE_NAME, - }, { name: QueueConstants.PROFILE_QUEUE_NAME, }, @@ -112,10 +109,6 @@ import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; name: QueueConstants.TOMBSTONE_QUEUE_NAME, adapter: BullMQAdapter, }), - BullBoardModule.forFeature({ - name: QueueConstants.UPDATE_QUEUE_NAME, - adapter: BullMQAdapter, - }), BullBoardModule.forFeature({ name: QueueConstants.PROFILE_QUEUE_NAME, adapter: BullMQAdapter, diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index c6a3a9db..e158de42 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -1,8 +1,8 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Injectable, Logger } from '@nestjs/common'; -import { Job } from 'bullmq'; +import { Job, Queue } from 'bullmq'; import Redis from 'ioredis'; -import { Processor } from '@nestjs/bullmq'; +import { InjectQueue, Processor } from '@nestjs/bullmq'; import { SchedulerRegistry } from '@nestjs/schedule'; import { CID } from 'multiformats'; import { hexToString } from '@polkadot/util'; @@ -16,6 +16,7 @@ import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; import { BlockchainService } from '../blockchain/blockchain.service'; import { RedisUtils } from '../utils/redis'; +import { Announcement } from '../interfaces/dsnp'; @Injectable() @Processor(QueueConstants.IPFS_QUEUE, { @@ -26,6 +27,11 @@ export class IPFSContentProcessor extends BaseConsumer { constructor( @InjectRedis() private redis: Redis, + @InjectQueue(QueueConstants.BROADCAST_QUEUE_NAME) private broadcastQueue: Queue, + @InjectQueue(QueueConstants.TOMBSTONE_QUEUE_NAME) private tombstoneQueue: Queue, + @InjectQueue(QueueConstants.REACTION_QUEUE_NAME) private reactionQueue: Queue, + @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, + @InjectQueue(QueueConstants.PROFILE_QUEUE_NAME) private profileQueue: Queue, private schedulerRegistry: SchedulerRegistry, private configService: ConfigService, private ipfsService: IpfsService, @@ -36,6 +42,7 @@ export class IPFSContentProcessor extends BaseConsumer { async process(job: Job): Promise { try { + this.checkHighWater(); this.logger.log(`IPFS Processing job ${job.id}`); this.logger.debug(`IPFS CID: ${job.data.cid} for schemaId: ${job.data.schemaId}`); const cid = CID.parse(job.data.cid); @@ -52,6 +59,7 @@ export class IPFSContentProcessor extends BaseConsumer { await this.redis.setex(schemaCacheKey, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, cachedSchema); } + // make sure schemaId is a valid one to prevent DoS const frequencySchema: PalletSchemasSchema = JSON.parse(cachedSchema); const hexString: string = Buffer.from(frequencySchema.model).toString('utf8'); const schema = JSON.parse(hexToString(hexString)); @@ -61,10 +69,22 @@ export class IPFSContentProcessor extends BaseConsumer { const reader = await parquet.ParquetReader.openBuffer(contentBuffer); const cursor = reader.getCursor(); - const records = []; + const records: Map = new Map(); + + const record = await cursor.next(); + while (record) { + const announcementRecordCast = record as Announcement; + if (records.has(announcementRecordCast.announcementType.toString())) { + records[announcementRecordCast.announcementType.toString()].push(announcementRecordCast); + } else { + records[announcementRecordCast.announcementType.toString()] = [announcementRecordCast]; + } + } } catch (e) { this.logger.error(`IPFS Job ${job.id} failed with error: ${e}`); throw e; } } + + private checkHighWater(): void {} } diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts index 62ad2f94..ffb6f399 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts @@ -55,7 +55,7 @@ import { IpfsService } from '../utils/ipfs.client'; { name: QueueConstants.IPFS_QUEUE, defaultJobOptions: { - attempts: 1, + attempts: 3, backoff: { type: 'exponential', }, @@ -75,9 +75,6 @@ import { IpfsService } from '../utils/ipfs.client'; { name: QueueConstants.PROFILE_QUEUE_NAME, }, - { - name: QueueConstants.UPDATE_QUEUE_NAME, - }, { name: QueueConstants.TOMBSTONE_QUEUE_NAME, }, diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index d5c972f2..0e7241b2 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -18,7 +18,6 @@ export namespace QueueConstants { export const BROADCAST_QUEUE_NAME = 'broadcastQueue'; export const REPLY_QUEUE_NAME = 'replyQueue'; export const REACTION_QUEUE_NAME = 'reactionQueue'; - export const UPDATE_QUEUE_NAME = 'updateQueue'; export const TOMBSTONE_QUEUE_NAME = 'tombstoneQueue'; export const PROFILE_QUEUE_NAME = 'profileQueue'; /** @@ -28,7 +27,6 @@ export namespace QueueConstants { [AnnouncementTypeDto.BROADCAST, BROADCAST_QUEUE_NAME], [AnnouncementTypeDto.REPLY, REPLY_QUEUE_NAME], [AnnouncementTypeDto.REACTION, REACTION_QUEUE_NAME], - [AnnouncementTypeDto.UPDATE, UPDATE_QUEUE_NAME], [AnnouncementTypeDto.TOMBSTONE, TOMBSTONE_QUEUE_NAME], [AnnouncementTypeDto.PROFILE, PROFILE_QUEUE_NAME], ]); @@ -39,7 +37,6 @@ export namespace QueueConstants { [BROADCAST_QUEUE_NAME, AnnouncementTypeDto.BROADCAST], [REPLY_QUEUE_NAME, AnnouncementTypeDto.REPLY], [REACTION_QUEUE_NAME, AnnouncementTypeDto.REACTION], - [UPDATE_QUEUE_NAME, AnnouncementTypeDto.UPDATE], [TOMBSTONE_QUEUE_NAME, AnnouncementTypeDto.TOMBSTONE], [PROFILE_QUEUE_NAME, AnnouncementTypeDto.PROFILE], ]); From 0ef824d7ee7ee1c5fa7f08ccf5c054f5b1e2a16f Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 15:06:58 -0500 Subject: [PATCH 078/137] cleanup --- services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index e158de42..f70affc0 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -42,7 +42,6 @@ export class IPFSContentProcessor extends BaseConsumer { async process(job: Job): Promise { try { - this.checkHighWater(); this.logger.log(`IPFS Processing job ${job.id}`); this.logger.debug(`IPFS CID: ${job.data.cid} for schemaId: ${job.data.schemaId}`); const cid = CID.parse(job.data.cid); @@ -86,5 +85,9 @@ export class IPFSContentProcessor extends BaseConsumer { } } - private checkHighWater(): void {} + private async isQueueFull(queue: Queue): Promise { + const highWater = this.configService.getQueueHighWater(); + const queueStats = await queue.getJobCounts(); + return queueStats.waiting + queueStats.active >= highWater; + } } From f55883290d08ca3d76a1a2d6b30e41b26f737e03 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 15:45:58 -0500 Subject: [PATCH 079/137] queue announcement types to be processed --- .../src/interfaces/announcement_response.ts | 7 ++ .../libs/common/src/ipfs/ipfs.dsnp.ts | 65 ++++++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 services/content-watcher/libs/common/src/interfaces/announcement_response.ts diff --git a/services/content-watcher/libs/common/src/interfaces/announcement_response.ts b/services/content-watcher/libs/common/src/interfaces/announcement_response.ts new file mode 100644 index 00000000..7888fa0d --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/announcement_response.ts @@ -0,0 +1,7 @@ +import { Announcement } from './dsnp'; + +export interface AnnouncementResponse { + requestId?: string; + schemaId: number; + announcement: Announcement; +} diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index f70affc0..823e8364 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -16,7 +16,8 @@ import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; import { BlockchainService } from '../blockchain/blockchain.service'; import { RedisUtils } from '../utils/redis'; -import { Announcement } from '../interfaces/dsnp'; +import { Announcement, AnnouncementType, BroadcastAnnouncement, ProfileAnnouncement, ReactionAnnouncement, ReplyAnnouncement, TombstoneAnnouncement } from '../interfaces/dsnp'; +import { AnnouncementResponse } from '../interfaces/announcement_response'; @Injectable() @Processor(QueueConstants.IPFS_QUEUE, { @@ -79,12 +80,74 @@ export class IPFSContentProcessor extends BaseConsumer { records[announcementRecordCast.announcementType.toString()] = [announcementRecordCast]; } } + + await this.buildAndQueueDSNPAnnouncements(records, schema, job.data); + + this.logger.log(`IPFS Job ${job.id} completed`); } catch (e) { this.logger.error(`IPFS Job ${job.id} failed with error: ${e}`); throw e; } } + private async buildAndQueueDSNPAnnouncements(records: Map, schema: any, jobData: IIPFSJob): Promise { + let jobRequestId = jobData.requestId; + if (!jobRequestId) { + jobRequestId = '🖨️ from Frequency'; + } + + records.forEach(async (mapRecord) => { + switch (mapRecord.announcementType) { + case AnnouncementType.Broadcast: { + const broadCastResponse: AnnouncementResponse = { + schemaId: jobData.schemaId, + announcement: mapRecord as BroadcastAnnouncement, + requestId: jobRequestId, + }; + await this.broadcastQueue.add('Broadcast', broadCastResponse); + break; + } + case AnnouncementType.Tombstone: { + const tombstoneResponse: AnnouncementResponse = { + schemaId: jobData.schemaId, + announcement: mapRecord as TombstoneAnnouncement, + requestId: jobRequestId, + }; + await this.tombstoneQueue.add('Tombstone', tombstoneResponse); + break; + } + case AnnouncementType.Reaction: { + const reactionResponse: AnnouncementResponse = { + schemaId: jobData.schemaId, + announcement: mapRecord as ReactionAnnouncement, + requestId: jobRequestId, + }; + await this.reactionQueue.add('Reaction', reactionResponse); + break; + } + case AnnouncementType.Reply: { + const replyResponse: AnnouncementResponse = { + schemaId: jobData.schemaId, + announcement: mapRecord as ReplyAnnouncement, + requestId: jobRequestId, + }; + await this.replyQueue.add('Reply', replyResponse); + break; + } + case AnnouncementType.Profile: { + const profileResponse: AnnouncementResponse = { + schemaId: jobData.schemaId, + announcement: mapRecord as ProfileAnnouncement, + requestId: jobRequestId, + }; + break; + } + default: + throw new Error(`Unknown announcement type ${mapRecord}`); + } + }); + } + private async isQueueFull(queue: Queue): Promise { const highWater = this.configService.getQueueHighWater(); const queueStats = await queue.getJobCounts(); From 6ff1ed8d39ec8d7c57494addb9d67b94bcf0def2 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 15:58:27 -0500 Subject: [PATCH 080/137] cleanup --- services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index 823e8364..0801c209 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -140,6 +140,7 @@ export class IPFSContentProcessor extends BaseConsumer { announcement: mapRecord as ProfileAnnouncement, requestId: jobRequestId, }; + this.profileQueue.add('Profile', profileResponse); break; } default: From 138ad240bc00e74fcc88030c5b85b65f5611d20a Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 16:00:33 -0500 Subject: [PATCH 081/137] update readme --- services/content-watcher/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index 94f38b05..dbdfb83d 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -1,10 +1,10 @@ -# Content Publisher +# Content Watcher -Content Publisher is a microservice designed to publish DSNP (Decentralized Social Networking Protocol) content to the Frequency blockchain. This README provides step-by-step instructions to set up and run the service. +Content Watcher is a service that watches for events on Frequency and produces DSNP content to respective output channels. ## Table of Contents -- [Content Publisher](#content-publisher) +- [Content Watcher](#content-watcher) - [Table of Contents](#table-of-contents) - [Prerequisites](#prerequisites) - [Getting Started](#getting-started) @@ -14,15 +14,15 @@ Content Publisher is a microservice designed to publish DSNP (Decentralized Soci Before you begin, ensure you have met the following requirements: -- **Docker:** Content Publisher is designed to run in a Docker environment. Make sure Docker is installed on your system. +- **Docker:** Content Watcher is designed to run in a Docker environment. Make sure Docker is installed on your system. ## Getting Started -Follow these steps to set up and run Content Publisher: +Follow these steps to set up and run Content Watcher: ### Clone the Repository -1. Clone the Content Publisher repository to your local machine: +1. Clone the Content Watcher repository to your local machine: ```bash git clone https://github.com/amplicalabls/content-watcher-service.git From 4667515f127726cd00b028c4e66cca421c11c15d Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 16:50:30 -0500 Subject: [PATCH 082/137] check if queues are not full before adding more messages --- .../libs/common/src/ipfs/ipfs.dsnp.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index 0801c209..f9d5b46e 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -104,7 +104,9 @@ export class IPFSContentProcessor extends BaseConsumer { announcement: mapRecord as BroadcastAnnouncement, requestId: jobRequestId, }; - await this.broadcastQueue.add('Broadcast', broadCastResponse); + if (!(await this.isQueueFull(this.broadcastQueue))) { + await this.broadcastQueue.add('Broadcast', broadCastResponse); + } break; } case AnnouncementType.Tombstone: { @@ -113,7 +115,9 @@ export class IPFSContentProcessor extends BaseConsumer { announcement: mapRecord as TombstoneAnnouncement, requestId: jobRequestId, }; - await this.tombstoneQueue.add('Tombstone', tombstoneResponse); + if (!(await this.isQueueFull(this.tombstoneQueue))) { + await this.tombstoneQueue.add('Tombstone', tombstoneResponse); + } break; } case AnnouncementType.Reaction: { @@ -122,7 +126,9 @@ export class IPFSContentProcessor extends BaseConsumer { announcement: mapRecord as ReactionAnnouncement, requestId: jobRequestId, }; - await this.reactionQueue.add('Reaction', reactionResponse); + if (!(await this.isQueueFull(this.reactionQueue))) { + await this.reactionQueue.add('Reaction', reactionResponse); + } break; } case AnnouncementType.Reply: { @@ -131,7 +137,9 @@ export class IPFSContentProcessor extends BaseConsumer { announcement: mapRecord as ReplyAnnouncement, requestId: jobRequestId, }; - await this.replyQueue.add('Reply', replyResponse); + if (!(await this.isQueueFull(this.replyQueue))) { + await this.replyQueue.add('Reply', replyResponse); + } break; } case AnnouncementType.Profile: { @@ -140,7 +148,9 @@ export class IPFSContentProcessor extends BaseConsumer { announcement: mapRecord as ProfileAnnouncement, requestId: jobRequestId, }; - this.profileQueue.add('Profile', profileResponse); + if (!(await this.isQueueFull(this.profileQueue))) { + this.profileQueue.add('Profile', profileResponse); + } break; } default: @@ -152,6 +162,11 @@ export class IPFSContentProcessor extends BaseConsumer { private async isQueueFull(queue: Queue): Promise { const highWater = this.configService.getQueueHighWater(); const queueStats = await queue.getJobCounts(); - return queueStats.waiting + queueStats.active >= highWater; + const canAddJobs = queueStats.waiting + queueStats.active >= highWater; + if (canAddJobs) { + this.logger.log(`Queue ${queue.name} is full`); + throw new Error(`Queue ${queue.name} is full`); + } + return canAddJobs; } } From 7acff74fbf76cf85beb783d2905087bf7d1d9aa9 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 16:52:37 -0500 Subject: [PATCH 083/137] simplify --- services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index f9d5b46e..dbf38941 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -7,7 +7,6 @@ import { SchedulerRegistry } from '@nestjs/schedule'; import { CID } from 'multiformats'; import { hexToString } from '@polkadot/util'; import { PalletSchemasSchema } from '@polkadot/types/lookup'; -import { fromFrequencySchema } from '@dsnp/frequency-schemas/parquet'; import parquet from '@dsnp/parquetjs'; import { ConfigService } from '../config/config.service'; import { QueueConstants } from '..'; @@ -92,10 +91,6 @@ export class IPFSContentProcessor extends BaseConsumer { private async buildAndQueueDSNPAnnouncements(records: Map, schema: any, jobData: IIPFSJob): Promise { let jobRequestId = jobData.requestId; - if (!jobRequestId) { - jobRequestId = '🖨️ from Frequency'; - } - records.forEach(async (mapRecord) => { switch (mapRecord.announcementType) { case AnnouncementType.Broadcast: { From 882465e06cfbee4fb45b9afbb4c4b1dd7a55a240 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 16:55:01 -0500 Subject: [PATCH 084/137] lint --- services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index dbf38941..c96aeba3 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -90,7 +90,7 @@ export class IPFSContentProcessor extends BaseConsumer { } private async buildAndQueueDSNPAnnouncements(records: Map, schema: any, jobData: IIPFSJob): Promise { - let jobRequestId = jobData.requestId; + const jobRequestId = jobData.requestId; records.forEach(async (mapRecord) => { switch (mapRecord.announcementType) { case AnnouncementType.Broadcast: { From 9391a25ec0edf40a0e2d54a03a01a721877d1f49 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 17:34:31 -0500 Subject: [PATCH 085/137] simplify ipfs queuing --- .../libs/common/src/crawler/crawler.ts | 2 - .../src/interfaces/ipfs.job.interface.ts | 14 +--- .../libs/common/src/scanner/scanner.ts | 76 +++++++++---------- 3 files changed, 40 insertions(+), 52 deletions(-) diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 45851ee8..5ae58865 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -123,10 +123,8 @@ export class CrawlerService extends BaseConsumer { } const ipfsQueueJob = createIPFSQueueJob( - schemaId.toNumber(), messageResponse.msa_id.unwrap().toString(), messageResponse.provider_msa_id.unwrap().toString(), - blockNumber.toBigInt(), messageResponse.cid.unwrap().toString(), messageResponse.index.toNumber(), id, diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index 1bb9af7e..4d276054 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -8,25 +8,15 @@ export interface IIPFSJob { requestId?: string; } -export function createIPFSQueueJob( - schemaId: number, - msaId: string, - providerId: string, - blockNumber: bigint, - cid: string, - index: number, - requestId: string, -): { key: string; data: IIPFSJob } { +export function createIPFSQueueJob(msaId: string, providerId: string, cid: string, index: number, requestId: string): { key: string; data: IIPFSJob } { return { - key: `${msaId}:${providerId}:${blockNumber}:${index}:${schemaId}`, + key: `${msaId}:${providerId}:${index}:${requestId}`, data: { msaId, providerId, cid, - blockNumber, index, requestId, - schemaId, } as IIPFSJob, }; } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index d81a79b3..6771169c 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -5,11 +5,12 @@ import Redis from 'ioredis'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; -import { u16, u32 } from '@polkadot/types'; -import { SchemaId } from '@frequency-chain/api-augment/interfaces'; +import { Vec, u16, u32 } from '@polkadot/types'; +import { BlockPaginationResponseMessage, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Queue } from 'bullmq'; import { firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; +import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; import { QueueConstants } from '../utils/queues'; @@ -101,9 +102,14 @@ export class ScannerService implements OnApplicationBootstrap { while (!this.paused && !latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { // eslint-disable-next-line no-await-in-loop - const events = await this.fetchEventsFromBlockchain(latestBlockHash); + const at = await this.blockchainService.apiPromise.at(latestBlockHash.toHex()); + // eslint-disable-next-line no-await-in-loop + const events = await at.query.system.events(); // eslint-disable-next-line no-await-in-loop const filteredEvents = await this.processEvents(events, eventsToWatch); + if (filteredEvents.length > 0) { + this.logger.log(`Found ${filteredEvents.length} events to process`); + } // eslint-disable-next-line no-await-in-loop await this.queueIPFSJobs(filteredEvents); // eslint-disable-next-line no-await-in-loop @@ -126,65 +132,59 @@ export class ScannerService implements OnApplicationBootstrap { } } - private async fetchEventsFromBlockchain(latestBlockHash: any) { - return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); - } - - private async processEvents(events: any, eventsToWatch: ChainWatchOptionsDto) { - const filteredEvents = await Promise.all( + private async processEvents(events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { + const filteredEvents: (Vec | null)[] = await Promise.all( events.map(async (event) => { - if (event.section === 'messages' && event.method === 'MessagesStored') { - if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.data[0].toString())) { + if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { + if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { return null; } - - const schemaId = event.data[0] as SchemaId; - const blockNumber = event.data[1] as BlockNumber; - const paginationRequest = { + const schemaId = event.event.data[0] as SchemaId; + const blockNumber = event.event.data[1] as BlockNumber; + let paginationRequest = { from_block: blockNumber.toBigInt(), from_index: 0, page_size: 1000, to_block: blockNumber.toBigInt() + 1n, }; - const messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - const senderMsaId = messageResponse.msa_id.unwrap().toString(); - const providerMsaId = messageResponse.provider_msa_id.unwrap().toString(); - - if (eventsToWatch.dsnpIds.length === 0 || eventsToWatch.dsnpIds.includes(senderMsaId) || eventsToWatch.dsnpIds.includes(providerMsaId)) { - return event; + const messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + this.logger.log(`message response: ${JSON.stringify(messageResponse)}`); + const messages: Vec = messageResponse.content; + while (messageResponse.has_next) { + paginationRequest = { + from_block: blockNumber.toBigInt(), + from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, + page_size: 1000, + to_block: blockNumber.toBigInt() + 1n, + }; + messages.push(...messageResponse.content); } + return messages; } return null; }), ); - return filteredEvents.filter((event) => event !== null); - } + const collectedMessages: MessageResponse[] = []; - private async queueIPFSJobs(events) { - const jobs = events.map(async (event) => { - const schemaId: u16 = event.data?.schemaId; - const blockNumber: u32 = event.data?.blockNumber; - const paginationRequest = { - from_block: blockNumber.toBigInt(), - from_index: 0, - page_size: 1000, - to_block: blockNumber.toBigInt() + 1n, - }; + filteredEvents.forEach((event) => { + if (event) { + collectedMessages.push(...event); + } + }); - // eslint-disable-next-line no-await-in-loop - const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); + return collectedMessages; + } + private async queueIPFSJobs(messages: MessageResponse[]) { + const jobs = messages.map(async (messageResponse) => { if (messageResponse.cid.isNone) { return; } - const ipfsQueueJob = createIPFSQueueJob( - schemaId.toNumber(), messageResponse.msa_id.unwrap().toString(), messageResponse.provider_msa_id.unwrap().toString(), - blockNumber.toBigInt(), messageResponse.cid.unwrap().toString(), messageResponse.index.toNumber(), '', From 436820b1bb289ac89035ed356ff34c7f3a84deb4 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 18:19:31 -0500 Subject: [PATCH 086/137] some cleanups --- .../libs/common/src/crawler/crawler.ts | 86 ++++++++++--------- .../libs/common/src/scanner/scanner.ts | 41 ++++----- 2 files changed, 65 insertions(+), 62 deletions(-) diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 5ae58865..b25b4a1d 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -3,11 +3,12 @@ import { Injectable } from '@nestjs/common'; import { InjectQueue, Processor } from '@nestjs/bullmq'; import Redis from 'ioredis'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; -import { u16, u32 } from '@polkadot/types'; -import { SchemaId } from '@frequency-chain/api-augment/interfaces'; +import { Vec, u16, u32 } from '@polkadot/types'; +import { BlockPaginationResponseMessage, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Job, Queue } from 'bullmq'; import { firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; +import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { BlockchainService } from '../blockchain/blockchain.service'; import { QueueConstants } from '../utils/queues'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; @@ -54,13 +55,12 @@ export class CrawlerService extends BaseConsumer { this.logger.debug(`Processing ${events.length} events for block ${blockNumber}`); // eslint-disable-next-line no-await-in-loop - const filteredEvents = await this.processEvents(events, filters); - if (filteredEvents.length === 0) { - this.logger.debug(`No events found for block ${blockNumber}`); - return; + const messages = await this.processEvents(events, filters); + if (messages.length > 0) { + this.logger.debug(`Found ${messages.length} messages for block ${blockNumber}`); } // eslint-disable-next-line no-await-in-loop - await this.queueIPFSJobs(id, filteredEvents); + await this.queueIPFSJobs(id, messages); promises.push(Promise.resolve()); }); @@ -72,68 +72,70 @@ export class CrawlerService extends BaseConsumer { return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); } - private async processEvents(events: any, eventsToWatch: ChainWatchOptionsDto) { - const filteredEvents = await Promise.all( + private async processEvents(events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { + const filteredEvents: (Vec | null)[] = await Promise.all( events.map(async (event) => { - if (event.section === 'messages' && event.method === 'MessagesStored') { - if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.data[0].toString())) { + if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { + if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { return null; } - - const schemaId = event.data[0] as SchemaId; - const blockNumber = event.data[1] as BlockNumber; - const paginationRequest = { + const schemaId = event.event.data[0] as SchemaId; + const blockNumber = event.event.data[1] as BlockNumber; + let paginationRequest = { from_block: blockNumber.toBigInt(), from_index: 0, page_size: 1000, to_block: blockNumber.toBigInt() + 1n, }; - const messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - const senderMsaId = messageResponse.msa_id.unwrap().toString(); - const providerMsaId = messageResponse.provider_msa_id.unwrap().toString(); - - if (eventsToWatch.dsnpIds.length === 0 || eventsToWatch.dsnpIds.includes(senderMsaId) || eventsToWatch.dsnpIds.includes(providerMsaId)) { - return event; + let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + const messages: Vec = messageResponse.content; + this.logger.error(JSON.stringify(messageResponse)); + while (messageResponse.has_next.toHuman()) { + paginationRequest = { + from_block: blockNumber.toBigInt(), + from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, + page_size: 1000, + to_block: blockNumber.toBigInt() + 1n, + }; + // eslint-disable-next-line no-await-in-loop + messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + if (messageResponse.content.length > 0) { + messages.push(...messageResponse.content); + } } + return messages; } return null; }), ); - return filteredEvents.filter((event) => event !== null); + const collectedMessages: MessageResponse[] = []; + filteredEvents.forEach((event) => { + if (event) { + collectedMessages.push(...event.toArray()); + } + }); + return collectedMessages; } - private async queueIPFSJobs(id: string, events) { - const jobs = events.map(async (event) => { - const schemaId: u16 = event.data?.schemaId; - const blockNumber: u32 = event.data?.blockNumber; - const paginationRequest = { - from_block: blockNumber.toBigInt(), - from_index: 0, - page_size: 1000, - to_block: blockNumber.toBigInt() + 1n, - }; - - // eslint-disable-next-line no-await-in-loop - const messageResponse = await firstValueFrom(this.blockchainService.api.rpc.messages.getBySchemaId(schemaId, paginationRequest)); - - if (messageResponse.cid.isNone) { + private async queueIPFSJobs(id: string, messages: MessageResponse[]) { + const promises = messages.map(async (messageResponse) => { + if (!messageResponse.cid || messageResponse.cid.isNone) { return; } const ipfsQueueJob = createIPFSQueueJob( - messageResponse.msa_id.unwrap().toString(), - messageResponse.provider_msa_id.unwrap().toString(), + messageResponse.msa_id.isNone ? messageResponse.provider_msa_id.toString() : messageResponse.msa_id.unwrap().toString(), + messageResponse.provider_msa_id.toString(), messageResponse.cid.unwrap().toString(), messageResponse.index.toNumber(), id, ); - // eslint-disable-next-line no-await-in-loop await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); }); - // eslint-disable-next-line no-await-in-loop - await Promise.all(jobs); + + await Promise.all(promises); } } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 6771169c..ac222769 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -5,10 +5,9 @@ import Redis from 'ioredis'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; -import { Vec, u16, u32 } from '@polkadot/types'; +import { Vec } from '@polkadot/types'; import { BlockPaginationResponseMessage, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Queue } from 'bullmq'; -import { firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { ConfigService } from '../config/config.service'; @@ -106,12 +105,12 @@ export class ScannerService implements OnApplicationBootstrap { // eslint-disable-next-line no-await-in-loop const events = await at.query.system.events(); // eslint-disable-next-line no-await-in-loop - const filteredEvents = await this.processEvents(events, eventsToWatch); - if (filteredEvents.length > 0) { - this.logger.log(`Found ${filteredEvents.length} events to process`); + const messages = await this.processEvents(events, eventsToWatch); + if (messages.length > 0) { + this.logger.debug(`Found ${messages.length} messages to process`); } // eslint-disable-next-line no-await-in-loop - await this.queueIPFSJobs(filteredEvents); + await this.queueIPFSJobs(messages); // eslint-disable-next-line no-await-in-loop await this.saveProgress(lastScannedBlock); lastScannedBlock += 1n; @@ -148,17 +147,21 @@ export class ScannerService implements OnApplicationBootstrap { to_block: blockNumber.toBigInt() + 1n, }; - const messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - this.logger.log(`message response: ${JSON.stringify(messageResponse)}`); + let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); const messages: Vec = messageResponse.content; - while (messageResponse.has_next) { + this.logger.error(JSON.stringify(messageResponse)); + while (messageResponse.has_next.toHuman()) { paginationRequest = { from_block: blockNumber.toBigInt(), from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, page_size: 1000, to_block: blockNumber.toBigInt() + 1n, }; - messages.push(...messageResponse.content); + // eslint-disable-next-line no-await-in-loop + messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + if (messageResponse.content.length > 0) { + messages.push(...messageResponse.content); + } } return messages; } @@ -167,34 +170,32 @@ export class ScannerService implements OnApplicationBootstrap { ); const collectedMessages: MessageResponse[] = []; - filteredEvents.forEach((event) => { if (event) { - collectedMessages.push(...event); + collectedMessages.push(...event.toArray()); } }); - return collectedMessages; } private async queueIPFSJobs(messages: MessageResponse[]) { - const jobs = messages.map(async (messageResponse) => { - if (messageResponse.cid.isNone) { + const promises = messages.map(async (messageResponse) => { + if (!messageResponse.cid || messageResponse.cid.isNone) { return; } + const ipfsQueueJob = createIPFSQueueJob( - messageResponse.msa_id.unwrap().toString(), - messageResponse.provider_msa_id.unwrap().toString(), + messageResponse.msa_id.isNone ? messageResponse.provider_msa_id.toString() : messageResponse.msa_id.unwrap().toString(), + messageResponse.provider_msa_id.toString(), messageResponse.cid.unwrap().toString(), messageResponse.index.toNumber(), '', ); - // eslint-disable-next-line no-await-in-loop await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); }); - // eslint-disable-next-line no-await-in-loop - await Promise.all(jobs); + + await Promise.all(promises); } private async getLastSeenBlockNumber(): Promise { From a0968658ddb2590b56d30c4e4c989e9eaa477104 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 17 Oct 2023 19:22:16 -0500 Subject: [PATCH 087/137] done --- .../apps/api/src/api.module.ts | 16 +++++++ .../src/blockchain/blockchain.service.ts | 2 +- .../libs/common/src/crawler/crawler.module.ts | 8 ++++ .../libs/common/src/crawler/crawler.ts | 1 - .../libs/common/src/ipfs/ipfs.dsnp.ts | 47 ++++--------------- .../libs/common/src/scanner/scanner.module.ts | 8 ++++ .../libs/common/src/scanner/scanner.ts | 1 - 7 files changed, 42 insertions(+), 41 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 8dd923bf..97b9b07d 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -59,9 +59,25 @@ import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; BullModule.registerQueue( { name: QueueConstants.REQUEST_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }, { name: QueueConstants.IPFS_QUEUE, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }, { name: QueueConstants.BROADCAST_QUEUE_NAME, diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 63270ad2..3b99f815 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -7,7 +7,7 @@ import { KeyringPair } from '@polkadot/keyring/types'; import { BlockHash, BlockNumber, DispatchError, DispatchInfo, Hash, SignedBlock } from '@polkadot/types/interfaces'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { AnyNumber, ISubmittableResult, RegistryError } from '@polkadot/types/types'; -import { u32, Option, u128 } from '@polkadot/types'; +import { u32, Option, u128, u16 } from '@polkadot/types'; import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo, PalletSchemasSchema } from '@polkadot/types/lookup'; import { ConfigService } from '../config/config.service'; import { Extrinsic } from './extrinsic'; diff --git a/services/content-watcher/libs/common/src/crawler/crawler.module.ts b/services/content-watcher/libs/common/src/crawler/crawler.module.ts index 86e00d09..cafeedea 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.module.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.module.ts @@ -40,6 +40,14 @@ import { QueueConstants } from '../utils/queues'; }), BullModule.registerQueue({ name: QueueConstants.IPFS_QUEUE, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }), BullModule.registerQueue({ name: QueueConstants.REQUEST_QUEUE_NAME, diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index b25b4a1d..e85d4ff7 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -90,7 +90,6 @@ export class CrawlerService extends BaseConsumer { let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); const messages: Vec = messageResponse.content; - this.logger.error(JSON.stringify(messageResponse)); while (messageResponse.has_next.toHuman()) { paginationRequest = { from_block: blockNumber.toBigInt(), diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index c96aeba3..33176fff 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -1,20 +1,13 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Injectable, Logger } from '@nestjs/common'; import { Job, Queue } from 'bullmq'; -import Redis from 'ioredis'; import { InjectQueue, Processor } from '@nestjs/bullmq'; -import { SchedulerRegistry } from '@nestjs/schedule'; -import { CID } from 'multiformats'; import { hexToString } from '@polkadot/util'; -import { PalletSchemasSchema } from '@polkadot/types/lookup'; import parquet from '@dsnp/parquetjs'; import { ConfigService } from '../config/config.service'; import { QueueConstants } from '..'; import { IIPFSJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; -import { BlockchainService } from '../blockchain/blockchain.service'; -import { RedisUtils } from '../utils/redis'; import { Announcement, AnnouncementType, BroadcastAnnouncement, ProfileAnnouncement, ReactionAnnouncement, ReplyAnnouncement, TombstoneAnnouncement } from '../interfaces/dsnp'; import { AnnouncementResponse } from '../interfaces/announcement_response'; @@ -26,16 +19,13 @@ export class IPFSContentProcessor extends BaseConsumer { public logger: Logger; constructor( - @InjectRedis() private redis: Redis, @InjectQueue(QueueConstants.BROADCAST_QUEUE_NAME) private broadcastQueue: Queue, @InjectQueue(QueueConstants.TOMBSTONE_QUEUE_NAME) private tombstoneQueue: Queue, @InjectQueue(QueueConstants.REACTION_QUEUE_NAME) private reactionQueue: Queue, @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, @InjectQueue(QueueConstants.PROFILE_QUEUE_NAME) private profileQueue: Queue, - private schedulerRegistry: SchedulerRegistry, private configService: ConfigService, private ipfsService: IpfsService, - private blockchainService: BlockchainService, ) { super(); } @@ -44,43 +34,24 @@ export class IPFSContentProcessor extends BaseConsumer { try { this.logger.log(`IPFS Processing job ${job.id}`); this.logger.debug(`IPFS CID: ${job.data.cid} for schemaId: ${job.data.schemaId}`); - const cid = CID.parse(job.data.cid); + const cidStr = hexToString(job.data.cid); + const contentBuffer = await this.ipfsService.getPinned(cidStr, true); - const ipfsHash = cid.toV0().toString(); - this.logger.debug(`IPFS Hash: ${ipfsHash}`); - - const contentBuffer = await this.ipfsService.getPinned(ipfsHash); - const schemaCacheKey = `schema:${job.data.schemaId}`; - let cachedSchema: string | null = await this.redis.get(schemaCacheKey); - if (!cachedSchema) { - const schemaResponse = await this.blockchainService.getSchema(job.data.schemaId); - cachedSchema = JSON.stringify(schemaResponse); - await this.redis.setex(schemaCacheKey, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, cachedSchema); - } - - // make sure schemaId is a valid one to prevent DoS - const frequencySchema: PalletSchemasSchema = JSON.parse(cachedSchema); - const hexString: string = Buffer.from(frequencySchema.model).toString('utf8'); - const schema = JSON.parse(hexToString(hexString)); - if (!schema) { - throw new Error(`Unable to parse schema for schemaId ${job.data.schemaId}`); + if(contentBuffer.byteLength === 0) { + this.logger.log(`IPFS Job ${job.id} completed with no content`); + return; } - const reader = await parquet.ParquetReader.openBuffer(contentBuffer); const cursor = reader.getCursor(); - const records: Map = new Map(); + const records: Announcement[] = []; const record = await cursor.next(); while (record) { const announcementRecordCast = record as Announcement; - if (records.has(announcementRecordCast.announcementType.toString())) { - records[announcementRecordCast.announcementType.toString()].push(announcementRecordCast); - } else { - records[announcementRecordCast.announcementType.toString()] = [announcementRecordCast]; - } + records.push(announcementRecordCast); } - await this.buildAndQueueDSNPAnnouncements(records, schema, job.data); + await this.buildAndQueueDSNPAnnouncements(records, job.data); this.logger.log(`IPFS Job ${job.id} completed`); } catch (e) { @@ -89,7 +60,7 @@ export class IPFSContentProcessor extends BaseConsumer { } } - private async buildAndQueueDSNPAnnouncements(records: Map, schema: any, jobData: IIPFSJob): Promise { + private async buildAndQueueDSNPAnnouncements(records: Announcement[], jobData: IIPFSJob): Promise { const jobRequestId = jobData.requestId; records.forEach(async (mapRecord) => { switch (mapRecord.announcementType) { diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts index b1354bfb..a34f0e10 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.module.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -40,6 +40,14 @@ import { QueueConstants } from '../utils/queues'; }), BullModule.registerQueue({ name: QueueConstants.IPFS_QUEUE, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }), ScheduleModule.forRoot(), ], diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index ac222769..6991b305 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -149,7 +149,6 @@ export class ScannerService implements OnApplicationBootstrap { let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); const messages: Vec = messageResponse.content; - this.logger.error(JSON.stringify(messageResponse)); while (messageResponse.has_next.toHuman()) { paginationRequest = { from_block: blockNumber.toBigInt(), From 4377daf79afe38b5f32bc0630bbc4d425c297e3f Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Wed, 18 Oct 2023 18:36:00 -0500 Subject: [PATCH 088/137] some renaming --- .../libs/common/src/utils/queues.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index 0e7241b2..7fa14455 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -4,22 +4,22 @@ export namespace QueueConstants { /** * Name of the queue that has all incoming IPFS messages from the blockchain */ - export const IPFS_QUEUE = 'ipfsQueue'; + export const IPFS_QUEUE = 'contentIpfsQueue'; /** * Name of the queue that has all incoming requests for specific announcements * from the blockchain */ - export const REQUEST_QUEUE_NAME = 'requestQueue'; + export const REQUEST_QUEUE_NAME = 'contentRequestQueue'; /** * Name of the queue that has all outgoing announcements from the blockchain */ - export const BROADCAST_QUEUE_NAME = 'broadcastQueue'; - export const REPLY_QUEUE_NAME = 'replyQueue'; - export const REACTION_QUEUE_NAME = 'reactionQueue'; - export const TOMBSTONE_QUEUE_NAME = 'tombstoneQueue'; - export const PROFILE_QUEUE_NAME = 'profileQueue'; + export const BROADCAST_QUEUE_NAME = 'watchBroadcastQueue'; + export const REPLY_QUEUE_NAME = 'watchReplyQueue'; + export const REACTION_QUEUE_NAME = 'watchReactionQueue'; + export const TOMBSTONE_QUEUE_NAME = 'watchTombstoneQueue'; + export const PROFILE_QUEUE_NAME = 'watchProfileQueue'; /** * Map between announcement type and it's queueName */ From 2ff2227ca0c2816f4f985002f6bf0cd8aadf0d2a Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 19 Oct 2023 10:51:50 -0500 Subject: [PATCH 089/137] cleanup --- .../content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index 33176fff..bfdac9f9 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -33,7 +33,10 @@ export class IPFSContentProcessor extends BaseConsumer { async process(job: Job): Promise { try { this.logger.log(`IPFS Processing job ${job.id}`); - this.logger.debug(`IPFS CID: ${job.data.cid} for schemaId: ${job.data.schemaId}`); + if(!job.data.cid) { + this.logger.error(`IPFS Job ${job.id} failed with no CID`); + return; + } const cidStr = hexToString(job.data.cid); const contentBuffer = await this.ipfsService.getPinned(cidStr, true); @@ -41,14 +44,15 @@ export class IPFSContentProcessor extends BaseConsumer { this.logger.log(`IPFS Job ${job.id} completed with no content`); return; } + const reader = await parquet.ParquetReader.openBuffer(contentBuffer); const cursor = reader.getCursor(); const records: Announcement[] = []; - - const record = await cursor.next(); + let record = await cursor.next(); while (record) { const announcementRecordCast = record as Announcement; records.push(announcementRecordCast); + record = await cursor.next(); } await this.buildAndQueueDSNPAnnouncements(records, job.data); From 4d47f493ffac8956474fc015141333d5d8eb1972 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 19 Oct 2023 11:04:56 -0500 Subject: [PATCH 090/137] assign job ids to announcements for uniqueness --- .../apps/api/src/api.service.ts | 4 ++-- .../libs/common/src/ipfs/ipfs.dsnp.ts | 22 ++++++++++++------- .../libs/common/src/utils/queues.ts | 6 +++++ 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 5ac4077b..195a998a 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -4,7 +4,7 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; -import { ContentSearchRequestDto, QueueConstants } from '../../../libs/common/src'; +import { ContentSearchRequestDto, QueueConstants, calculateJobId } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @@ -44,7 +44,7 @@ export class ApiService { } public async searchContent(contentSearchRequestDto: ContentSearchRequestDto) { - const jobId = contentSearchRequestDto.id ?? this.calculateJobId(contentSearchRequestDto); + const jobId = contentSearchRequestDto.id ?? calculateJobId(contentSearchRequestDto); this.logger.debug(`Searching for content with request ${JSON.stringify(contentSearchRequestDto)}`); const job = await this.requestQueue.getJob(jobId); diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index bfdac9f9..b4a4a674 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -4,7 +4,7 @@ import { InjectQueue, Processor } from '@nestjs/bullmq'; import { hexToString } from '@polkadot/util'; import parquet from '@dsnp/parquetjs'; import { ConfigService } from '../config/config.service'; -import { QueueConstants } from '..'; +import { QueueConstants, calculateJobId } from '..'; import { IIPFSJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; @@ -33,14 +33,14 @@ export class IPFSContentProcessor extends BaseConsumer { async process(job: Job): Promise { try { this.logger.log(`IPFS Processing job ${job.id}`); - if(!job.data.cid) { + if (!job.data.cid) { this.logger.error(`IPFS Job ${job.id} failed with no CID`); return; } const cidStr = hexToString(job.data.cid); const contentBuffer = await this.ipfsService.getPinned(cidStr, true); - if(contentBuffer.byteLength === 0) { + if (contentBuffer.byteLength === 0) { this.logger.log(`IPFS Job ${job.id} completed with no content`); return; } @@ -52,6 +52,7 @@ export class IPFSContentProcessor extends BaseConsumer { while (record) { const announcementRecordCast = record as Announcement; records.push(announcementRecordCast); + // eslint-disable-next-line no-await-in-loop record = await cursor.next(); } @@ -75,7 +76,8 @@ export class IPFSContentProcessor extends BaseConsumer { requestId: jobRequestId, }; if (!(await this.isQueueFull(this.broadcastQueue))) { - await this.broadcastQueue.add('Broadcast', broadCastResponse); + const jobId = calculateJobId(broadCastResponse); + await this.broadcastQueue.add('Broadcast', broadCastResponse, { jobId }); } break; } @@ -86,7 +88,8 @@ export class IPFSContentProcessor extends BaseConsumer { requestId: jobRequestId, }; if (!(await this.isQueueFull(this.tombstoneQueue))) { - await this.tombstoneQueue.add('Tombstone', tombstoneResponse); + const jobId = calculateJobId(tombstoneResponse); + await this.tombstoneQueue.add('Tombstone', tombstoneResponse, { jobId }); } break; } @@ -97,7 +100,8 @@ export class IPFSContentProcessor extends BaseConsumer { requestId: jobRequestId, }; if (!(await this.isQueueFull(this.reactionQueue))) { - await this.reactionQueue.add('Reaction', reactionResponse); + const jobId = calculateJobId(reactionResponse); + await this.reactionQueue.add('Reaction', reactionResponse, { jobId }); } break; } @@ -108,7 +112,8 @@ export class IPFSContentProcessor extends BaseConsumer { requestId: jobRequestId, }; if (!(await this.isQueueFull(this.replyQueue))) { - await this.replyQueue.add('Reply', replyResponse); + const jobId = calculateJobId(replyResponse); + await this.replyQueue.add('Reply', replyResponse, { jobId }); } break; } @@ -119,7 +124,8 @@ export class IPFSContentProcessor extends BaseConsumer { requestId: jobRequestId, }; if (!(await this.isQueueFull(this.profileQueue))) { - this.profileQueue.add('Profile', profileResponse); + const jobId = calculateJobId(profileResponse); + this.profileQueue.add('Profile', profileResponse, { jobId }); } break; } diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index 7fa14455..cb79ab1c 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -1,3 +1,4 @@ +import { createHash } from 'crypto'; import { AnnouncementTypeDto } from '../dtos/common.dto'; export namespace QueueConstants { @@ -41,3 +42,8 @@ export namespace QueueConstants { [PROFILE_QUEUE_NAME, AnnouncementTypeDto.PROFILE], ]); } + +export const calculateJobId = (jobWithoutId: any): string => { + const stringVal = JSON.stringify(jobWithoutId); + return createHash('sha1').update(stringVal).digest('base64url'); +}; From af3eedf1bc032f61a435c26ec33581b3e950ac5d Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 19 Oct 2023 11:15:17 -0500 Subject: [PATCH 091/137] add update type announcement --- .../apps/api/src/api.module.ts | 7 ++++ .../libs/common/src/ipfs/ipfs.dsnp.ts | 33 +++++++++++++++---- .../libs/common/src/ipfs/ipfs.module.ts | 3 ++ .../libs/common/src/utils/queues.ts | 3 ++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 97b9b07d..4e043ac3 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -94,6 +94,9 @@ import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; { name: QueueConstants.PROFILE_QUEUE_NAME, }, + { + name: QueueConstants.UPDATE_QUEUE_NAME, + }, ), // Bullboard UI @@ -129,6 +132,10 @@ import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; name: QueueConstants.PROFILE_QUEUE_NAME, adapter: BullMQAdapter, }), + BullBoardModule.forFeature({ + name: QueueConstants.UPDATE_QUEUE_NAME, + adapter: BullMQAdapter, + }), EventEmitterModule.forRoot({ // Use this instance throughout the application global: true, diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index b4a4a674..e26381c7 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -8,7 +8,16 @@ import { QueueConstants, calculateJobId } from '..'; import { IIPFSJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; -import { Announcement, AnnouncementType, BroadcastAnnouncement, ProfileAnnouncement, ReactionAnnouncement, ReplyAnnouncement, TombstoneAnnouncement } from '../interfaces/dsnp'; +import { + Announcement, + AnnouncementType, + BroadcastAnnouncement, + ProfileAnnouncement, + ReactionAnnouncement, + ReplyAnnouncement, + TombstoneAnnouncement, + UpdateAnnouncement, +} from '../interfaces/dsnp'; import { AnnouncementResponse } from '../interfaces/announcement_response'; @Injectable() @@ -24,6 +33,7 @@ export class IPFSContentProcessor extends BaseConsumer { @InjectQueue(QueueConstants.REACTION_QUEUE_NAME) private reactionQueue: Queue, @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, @InjectQueue(QueueConstants.PROFILE_QUEUE_NAME) private profileQueue: Queue, + @InjectQueue(QueueConstants.UPDATE_QUEUE_NAME) private updateQueue: Queue, private configService: ConfigService, private ipfsService: IpfsService, ) { @@ -47,11 +57,10 @@ export class IPFSContentProcessor extends BaseConsumer { const reader = await parquet.ParquetReader.openBuffer(contentBuffer); const cursor = reader.getCursor(); - const records: Announcement[] = []; + const records: any[] = []; let record = await cursor.next(); while (record) { - const announcementRecordCast = record as Announcement; - records.push(announcementRecordCast); + records.push(record); // eslint-disable-next-line no-await-in-loop record = await cursor.next(); } @@ -65,7 +74,7 @@ export class IPFSContentProcessor extends BaseConsumer { } } - private async buildAndQueueDSNPAnnouncements(records: Announcement[], jobData: IIPFSJob): Promise { + private async buildAndQueueDSNPAnnouncements(records: any[], jobData: IIPFSJob): Promise { const jobRequestId = jobData.requestId; records.forEach(async (mapRecord) => { switch (mapRecord.announcementType) { @@ -129,8 +138,20 @@ export class IPFSContentProcessor extends BaseConsumer { } break; } + case AnnouncementType.Update: { + const updateResponse: AnnouncementResponse = { + schemaId: jobData.schemaId, + announcement: mapRecord as UpdateAnnouncement, + requestId: jobRequestId, + }; + if (!(await this.isQueueFull(this.profileQueue))) { + const jobId = calculateJobId(updateResponse); + this.updateQueue.add('Update', updateResponse, { jobId }); + } + break; + } default: - throw new Error(`Unknown announcement type ${mapRecord}`); + throw new Error(`Unknown announcement type ${JSON.stringify(mapRecord)}`); } }); } diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts index ffb6f399..98a27b00 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts @@ -78,6 +78,9 @@ import { IpfsService } from '../utils/ipfs.client'; { name: QueueConstants.TOMBSTONE_QUEUE_NAME, }, + { + name: QueueConstants.UPDATE_QUEUE_NAME, + }, ), ], controllers: [], diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index cb79ab1c..77019b3a 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -21,6 +21,7 @@ export namespace QueueConstants { export const REACTION_QUEUE_NAME = 'watchReactionQueue'; export const TOMBSTONE_QUEUE_NAME = 'watchTombstoneQueue'; export const PROFILE_QUEUE_NAME = 'watchProfileQueue'; + export const UPDATE_QUEUE_NAME = 'watchUpdateQueue'; /** * Map between announcement type and it's queueName */ @@ -30,6 +31,7 @@ export namespace QueueConstants { [AnnouncementTypeDto.REACTION, REACTION_QUEUE_NAME], [AnnouncementTypeDto.TOMBSTONE, TOMBSTONE_QUEUE_NAME], [AnnouncementTypeDto.PROFILE, PROFILE_QUEUE_NAME], + [AnnouncementTypeDto.UPDATE, UPDATE_QUEUE_NAME], ]); /** * Map between queue name and it's announcement type @@ -40,6 +42,7 @@ export namespace QueueConstants { [REACTION_QUEUE_NAME, AnnouncementTypeDto.REACTION], [TOMBSTONE_QUEUE_NAME, AnnouncementTypeDto.TOMBSTONE], [PROFILE_QUEUE_NAME, AnnouncementTypeDto.PROFILE], + [UPDATE_QUEUE_NAME, AnnouncementTypeDto.UPDATE], ]); } From b8abfc8b2c74243040622ce06fe342ba6709499c Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 19 Oct 2023 14:20:49 -0500 Subject: [PATCH 092/137] add some webhook related apis --- .../apps/api/src/api.controller.ts | 35 +++++++++++++++++- .../apps/api/src/api.service.ts | 37 ++++++++++++++++--- .../libs/common/src/config/swagger_config.ts | 3 ++ .../libs/common/src/constants.ts | 6 +++ .../src/dtos/subscription.webhook.dto.ts | 19 ++++++++++ 5 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 28f124f0..e644e812 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,8 +1,9 @@ -import { Body, Controller, Get, HttpStatus, Logger, Post, Put } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpStatus, Logger, Post, Put } from '@nestjs/common'; import { ApiBody, ApiOkResponse } from '@nestjs/swagger'; import { ApiService } from './api.service'; import { ResetScannerDto, ContentSearchRequestDto } from '../../../libs/common/src'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; +import { WebhookRegistrationDto } from '../../../libs/common/src/dtos/subscription.webhook.dto'; @Controller('api') export class ApiController { @@ -65,4 +66,36 @@ export class ApiController { jobId: jobResult.id, }; } + + @Put('registerWebhook') + @ApiBody({ + description: 'Register a webhook to be called when a new content is created', + type: WebhookRegistrationDto, + }) + async registerWebhook(@Body() webhookRegistrationDto: WebhookRegistrationDto) { + this.logger.debug(`Registering webhook ${JSON.stringify(webhookRegistrationDto)}`); + await this.apiService.setWebhook(webhookRegistrationDto); + return { + status: HttpStatus.OK, + }; + } + + @Delete('clearAllWebHooks') + async clearAllWebHooks() { + this.logger.debug('Unregistering webhooks'); + await this.apiService.clearAllWebhooks(); + return { + status: HttpStatus.OK, + }; + } + + @Get('getRegisteredWebhooks') + async getRegisteredWebhooks() { + this.logger.debug('Getting registered webhooks'); + const registeredWebhooks = await this.apiService.getRegisteredWebhooks(); + return { + status: HttpStatus.OK, + registeredWebhooks, + }; + } } diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 195a998a..2e1174a5 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -6,8 +6,10 @@ import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { ContentSearchRequestDto, QueueConstants, calculateJobId } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner'; -import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../../../libs/common/src/constants'; +import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../../../libs/common/src/constants'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; +import { WebhookRegistrationDto } from '../../../libs/common/src/dtos/subscription.webhook.dto'; +import { AnnouncementType } from '../../../libs/common/src/interfaces/dsnp'; @Injectable() export class ApiService { @@ -58,9 +60,34 @@ export class ApiService { return jobPromise; } - // eslint-disable-next-line class-methods-use-this - private calculateJobId(jobWithoutId: ContentSearchRequestDto): string { - const stringVal = JSON.stringify(jobWithoutId); - return createHash('sha1').update(stringVal).digest('base64url'); + public async setWebhook(webhookRegistration: WebhookRegistrationDto) { + const webhookId = createHash('sha256').update(webhookRegistration.url).digest('hex'); + this.logger.debug(`Setting webhook ${webhookId} to ${JSON.stringify(webhookRegistration)}`); + const currentRegistedWebooks = await this.redis.get(REGISTERED_WEBHOOK_KEY); + + let currentWebhookRegistrationDtos: { announcementType: string; urls: string[] }[] = []; + if (currentRegistedWebooks) { + currentWebhookRegistrationDtos = JSON.parse(currentRegistedWebooks); + } + + webhookRegistration.announcementTypes.forEach((announcementType) => { + const index = currentWebhookRegistrationDtos.findIndex((webhookRegistrationDto) => webhookRegistrationDto.announcementType === announcementType.toLowerCase()); + if (index === -1) { + currentWebhookRegistrationDtos.push({ announcementType: announcementType.toLowerCase(), urls: [webhookRegistration.url] }); + } else { + currentWebhookRegistrationDtos[index].urls.push(webhookRegistration.url); + } + }); + } + + public async clearAllWebhooks() { + this.logger.debug('Clearing all webhooks'); + await this.redis.del(REGISTERED_WEBHOOK_KEY); + } + + public async getRegisteredWebhooks() { + this.logger.debug('Getting registered webhooks'); + const registeredWebhooks = await this.redis.get(REGISTERED_WEBHOOK_KEY); + return registeredWebhooks ? JSON.parse(registeredWebhooks) : []; } } diff --git a/services/content-watcher/libs/common/src/config/swagger_config.ts b/services/content-watcher/libs/common/src/config/swagger_config.ts index ca5648e5..af255934 100644 --- a/services/content-watcher/libs/common/src/config/swagger_config.ts +++ b/services/content-watcher/libs/common/src/config/swagger_config.ts @@ -17,5 +17,8 @@ export const initSwagger = async (app: INestApplication, apiPath: string) => { const document = SwaggerModule.createDocument(app, options, { extraModels: [], }); + + const fs = require('fs'); + fs.writeFileSync('./swagger-spec.json', JSON.stringify(document)); SwaggerModule.setup(apiPath, app, document); }; diff --git a/services/content-watcher/libs/common/src/constants.ts b/services/content-watcher/libs/common/src/constants.ts index 334e2e06..c9856cdd 100644 --- a/services/content-watcher/libs/common/src/constants.ts +++ b/services/content-watcher/libs/common/src/constants.ts @@ -18,3 +18,9 @@ export const LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY: string = 'lastSeenBlockNumberSc * @type {string} */ export const EVENTS_TO_WATCH_KEY: string = 'eventsToWatch'; + +/** + * Registered Webhook key for Redis + * @type {string} + */ +export const REGISTERED_WEBHOOK_KEY: string = 'registeredWebhook'; diff --git a/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts b/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts new file mode 100644 index 00000000..b6e53626 --- /dev/null +++ b/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts @@ -0,0 +1,19 @@ +import { IsArray, IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; + +// WebhookRegistrationDto.ts +export class WebhookRegistrationDto { + @IsString() + @ApiProperty({ + description: 'Webhook URL', + example: 'https://example.com/webhook', + }) + url: string; // Webhook URL + + @IsArray() + @ApiProperty({ + description: 'Announcement types to send to the webhook', + example: ['Broadcast', 'Reaction'], + }) + announcementTypes: string[]; // Announcement types to send to the webhook +} From 9c2709697eae9042a7f81d95e18bf426a97ecb3d Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 19 Oct 2023 14:29:22 -0500 Subject: [PATCH 093/137] add API for webhooks and swagger update --- .../apps/api/src/api.controller.ts | 4 ++ .../apps/api/src/api.service.ts | 4 +- .../libs/common/src/config/swagger_config.ts | 3 -- services/content-watcher/swagger.yaml | 52 +++++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index e644e812..8dfa8970 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -90,6 +90,10 @@ export class ApiController { } @Get('getRegisteredWebhooks') + @ApiOkResponse({ + description: 'Returns a list of registered webhooks', + type: [WebhookRegistrationDto], + }) async getRegisteredWebhooks() { this.logger.debug('Getting registered webhooks'); const registeredWebhooks = await this.apiService.getRegisteredWebhooks(); diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 2e1174a5..4bdc7675 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -78,6 +78,8 @@ export class ApiService { currentWebhookRegistrationDtos[index].urls.push(webhookRegistration.url); } }); + + await this.redis.set(REGISTERED_WEBHOOK_KEY, JSON.stringify(currentWebhookRegistrationDtos)); } public async clearAllWebhooks() { @@ -85,7 +87,7 @@ export class ApiService { await this.redis.del(REGISTERED_WEBHOOK_KEY); } - public async getRegisteredWebhooks() { + public async getRegisteredWebhooks(): Promise { this.logger.debug('Getting registered webhooks'); const registeredWebhooks = await this.redis.get(REGISTERED_WEBHOOK_KEY); return registeredWebhooks ? JSON.parse(registeredWebhooks) : []; diff --git a/services/content-watcher/libs/common/src/config/swagger_config.ts b/services/content-watcher/libs/common/src/config/swagger_config.ts index af255934..ca5648e5 100644 --- a/services/content-watcher/libs/common/src/config/swagger_config.ts +++ b/services/content-watcher/libs/common/src/config/swagger_config.ts @@ -17,8 +17,5 @@ export const initSwagger = async (app: INestApplication, apiPath: string) => { const document = SwaggerModule.createDocument(app, options, { extraModels: [], }); - - const fs = require('fs'); - fs.writeFileSync('./swagger-spec.json', JSON.stringify(document)); SwaggerModule.setup(apiPath, app, document); }; diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index cdf4e96b..73e460b2 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -67,6 +67,40 @@ paths: application/json: schema: type: string + /api/registerWebhook: + put: + operationId: ApiController_registerWebhook + parameters: [] + requestBody: + required: true + description: Register a webhook to be called when a new content is created + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookRegistrationDto' + responses: + '200': + description: '' + /api/clearAllWebHooks: + delete: + operationId: ApiController_clearAllWebHooks + parameters: [] + responses: + '200': + description: '' + /api/getRegisteredWebhooks: + get: + operationId: ApiController_getRegisteredWebhooks + parameters: [] + responses: + '200': + description: Returns a list of registered webhooks + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/WebhookRegistrationDto' info: title: Content Watcher Service API description: Content Watcher Service API @@ -135,4 +169,22 @@ components: - startBlock - endBlock - filters + WebhookRegistrationDto: + type: object + properties: + url: + type: string + description: Webhook URL + example: https://example.com/webhook + announcementTypes: + description: Announcement types to send to the webhook + example: + - Broadcast + - Reaction + type: array + items: + type: string + required: + - url + - announcementTypes responses: {} From a53c978c717975aab1136a927d280398b019987b Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 19 Oct 2023 15:53:25 -0500 Subject: [PATCH 094/137] pubsub mechanism to send annoucement to webhooks --- services/content-watcher/.env.docker.dev | 1 + .../apps/api/src/api.module.ts | 2 + services/content-watcher/env.template | 2 +- .../common/src/config/config.service.spec.ts | 1 + .../libs/common/src/config/config.service.ts | 5 + .../libs/common/src/config/env.config.ts | 1 + .../libs/common/src/crawler/crawler.ts | 6 + .../common/src/pubsub/announcers/broadcast.ts | 26 +++ .../common/src/pubsub/announcers/profile.ts | 26 +++ .../common/src/pubsub/announcers/reaction.ts | 26 +++ .../common/src/pubsub/announcers/reply.ts | 26 +++ .../common/src/pubsub/announcers/tombstone.ts | 26 +++ .../common/src/pubsub/announcers/update.ts | 26 +++ .../libs/common/src/pubsub/pubsub.module.ts | 148 ++++++++++++++++++ .../libs/common/src/pubsub/pubsub.service.ts | 57 +++++++ .../libs/common/src/scanner/scanner.ts | 7 +- 16 files changed, 384 insertions(+), 2 deletions(-) create mode 100644 services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts create mode 100644 services/content-watcher/libs/common/src/pubsub/announcers/profile.ts create mode 100644 services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts create mode 100644 services/content-watcher/libs/common/src/pubsub/announcers/reply.ts create mode 100644 services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts create mode 100644 services/content-watcher/libs/common/src/pubsub/announcers/update.ts create mode 100644 services/content-watcher/libs/common/src/pubsub/pubsub.module.ts create mode 100644 services/content-watcher/libs/common/src/pubsub/pubsub.service.ts diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.docker.dev index 0ffd0ded..d5f44db9 100644 --- a/services/content-watcher/.env.docker.dev +++ b/services/content-watcher/.env.docker.dev @@ -16,5 +16,6 @@ QUEUE_HIGH_WATER=1000 HEALTH_CHECK_SUCCESS_THRESHOLD=10 HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRIES=4 +WEB_HOOK_POST_MAX_RETRIES=4 ENVIRONMENT="dev" API_PORT=3000 diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 4e043ac3..46c2f092 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -15,6 +15,7 @@ import { ScannerModule } from '../../../libs/common/src/scanner/scanner.module'; import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; import { CrawlerModule } from '../../../libs/common/src/crawler/crawler.module'; import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; +import { PubSubModule } from '../../../libs/common/src/pubsub/pubsub.module'; @Module({ imports: [ @@ -23,6 +24,7 @@ import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; ScannerModule, CrawlerModule, IPFSProcessorModule, + PubSubModule, RedisModule.forRootAsync( { imports: [ConfigModule], diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index 331273b8..e24a12b0 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -16,7 +16,7 @@ QUEUE_HIGH_WATER=1000 HEALTH_CHECK_SUCCESS_THRESHOLD=10 HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 HEALTH_CHECK_MAX_RETRIES=4 - +WEB_HOOK_POST_MAX_RETRIES=4 API_PORT=3000 # should be dev for e2e tests. Options [dev, rococo, mainnet] diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index e81c50bb..d52ed80c 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -49,6 +49,7 @@ describe('ContentWatcherConfigService', () => { HEALTH_CHECK_SUCCESS_THRESHOLD: undefined, HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: undefined, HEALTH_CHECK_MAX_RETRIES: undefined, + WEB_HOOK_POST_MAX_RETRIES: undefined, API_PORT: undefined, }; diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index a1263a49..d6625423 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -20,6 +20,7 @@ export interface ConfigEnvironmentVariables { HEALTH_CHECK_SUCCESS_THRESHOLD: number; HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: number; HEALTH_CHECK_MAX_RETRIES: number; + WEB_HOOK_POST_MAX_RETRIES: number; API_PORT: number; } @@ -95,4 +96,8 @@ export class ConfigService { public getApiPort(): number { return this.nestConfigService.get('API_PORT')!; } + + public getWebookMaxRetries(): number { + return this.nestConfigService.get('WEB_HOOK_POST_MAX_RETRIES')!; + } } diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 8d87146e..519cfbd6 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -22,6 +22,7 @@ export const configModuleOptions: ConfigModuleOptions = { HEALTH_CHECK_SUCCESS_THRESHOLD: Joi.number().min(1).default(10), HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(64), HEALTH_CHECK_MAX_RETRIES: Joi.number().min(0).default(20), + WEB_HOOK_POST_MAX_RETRIES: Joi.number().min(0).default(3), API_PORT: Joi.number().min(0).default(3000), }), }; diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index e85d4ff7..35b4d470 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -15,6 +15,7 @@ import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { ContentSearchRequestDto } from '../dtos/request-job.dto'; +import { REGISTERED_WEBHOOK_KEY } from '../constants'; @Injectable() @Processor(QueueConstants.REQUEST_QUEUE_NAME, { @@ -31,6 +32,11 @@ export class CrawlerService extends BaseConsumer { async process(job: Job): Promise { this.logger.log(`Processing crawler job ${job.id}`); + const registeredWebhook = await this.cache.get(REGISTERED_WEBHOOK_KEY); + + if (!registeredWebhook) { + throw new Error('No registered webhook to send data to'); + } const blockList: bigint[] = []; const blockStart = BigInt(job.data.startBlock); const blockEnd = BigInt(job.data.endBlock); diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts b/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts new file mode 100644 index 00000000..635c1bde --- /dev/null +++ b/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts @@ -0,0 +1,26 @@ +import { Processor } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job } from 'bullmq'; +import { QueueConstants } from '../..'; +import { BaseConsumer } from '../../utils/base-consumer'; +import { AnnouncementResponse } from '../../interfaces/announcement_response'; +import { PubSubService } from '../pubsub.service'; + +@Injectable() +@Processor(QueueConstants.BROADCAST_QUEUE_NAME, { concurrency: 2 }) +export class BroadcastSubscriber extends BaseConsumer { + constructor(private readonly pubsubService: PubSubService) { + super(); + } + + async process(job: Job): Promise { + this.logger.debug(`Sending 🔊 broadcasts to registered webhooks`); + try { + await this.pubsubService.process(job.data, 'broadcast'); // job.name is 'broadcast + this.logger.debug(`Broadcast sent to registered webhooks`); + } catch (error) { + this.logger.error(`Failed to send broadcast to registered webhooks`); + throw error; + } + } +} diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts b/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts new file mode 100644 index 00000000..ee15e27f --- /dev/null +++ b/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts @@ -0,0 +1,26 @@ +import { Processor } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job } from 'bullmq'; +import { QueueConstants } from '../..'; +import { BaseConsumer } from '../../utils/base-consumer'; +import { AnnouncementResponse } from '../../interfaces/announcement_response'; +import { PubSubService } from '../pubsub.service'; + +@Injectable() +@Processor(QueueConstants.PROFILE_QUEUE_NAME, { concurrency: 2 }) +export class ProfileSubscriber extends BaseConsumer { + constructor(private readonly pubsubService: PubSubService) { + super(); + } + + async process(job: Job): Promise { + this.logger.debug(`Sending 🔲 profiles to registered webhooks`); + try { + await this.pubsubService.process(job.data, 'profile'); + this.logger.debug(`Profile sent to registered webhooks`); + } catch (error) { + this.logger.error(`Failed to send profile to registered webhooks`); + throw error; + } + } +} diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts b/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts new file mode 100644 index 00000000..a9325fd9 --- /dev/null +++ b/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts @@ -0,0 +1,26 @@ +import { Processor } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job } from 'bullmq'; +import { QueueConstants } from '../..'; +import { BaseConsumer } from '../../utils/base-consumer'; +import { AnnouncementResponse } from '../../interfaces/announcement_response'; +import { PubSubService } from '../pubsub.service'; + +@Injectable() +@Processor(QueueConstants.REACTION_QUEUE_NAME, { concurrency: 2 }) +export class ReactionSubscriber extends BaseConsumer { + constructor(private readonly pubsubService: PubSubService) { + super(); + } + + async process(job: Job): Promise { + this.logger.debug(`Sending 🧪 reactions to registered webhooks`); + try { + await this.pubsubService.process(job.data, 'reaction'); + this.logger.debug(`Reaction sent to registered webhooks`); + } catch (error) { + this.logger.error(`Failed to send reaction to registered webhooks`); + throw error; + } + } +} diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts b/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts new file mode 100644 index 00000000..e1f79b6b --- /dev/null +++ b/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts @@ -0,0 +1,26 @@ +import { Processor } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job } from 'bullmq'; +import { QueueConstants } from '../..'; +import { BaseConsumer } from '../../utils/base-consumer'; +import { AnnouncementResponse } from '../../interfaces/announcement_response'; +import { PubSubService } from '../pubsub.service'; + +@Injectable() +@Processor(QueueConstants.REPLY_QUEUE_NAME, { concurrency: 2 }) +export class ReplySubscriber extends BaseConsumer { + constructor(private readonly pubsubService: PubSubService) { + super(); + } + + async process(job: Job): Promise { + this.logger.debug(`Sending 📩 reply to registered webhooks`); + try { + await this.pubsubService.process(job.data, 'reply'); + this.logger.debug(`Reply sent to registered webhooks`); + } catch (error) { + this.logger.error(`Failed to send reply to registered webhooks`); + throw error; + } + } +} diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts b/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts new file mode 100644 index 00000000..678f1535 --- /dev/null +++ b/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts @@ -0,0 +1,26 @@ +import { Processor } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job } from 'bullmq'; +import { QueueConstants } from '../..'; +import { BaseConsumer } from '../../utils/base-consumer'; +import { AnnouncementResponse } from '../../interfaces/announcement_response'; +import { PubSubService } from '../pubsub.service'; + +@Injectable() +@Processor(QueueConstants.TOMBSTONE_QUEUE_NAME, { concurrency: 2 }) +export class TomstoneSubscriber extends BaseConsumer { + constructor(private readonly pubsubService: PubSubService) { + super(); + } + + async process(job: Job): Promise { + this.logger.debug(`Sending 💀 tombstone to registered webhooks`); + try { + await this.pubsubService.process(job.data, 'tombstone'); + this.logger.debug(`Tombstone sent to registered webhooks`); + } catch (error) { + this.logger.error(`Failed to send tombstone to registered webhooks`); + throw error; + } + } +} diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/update.ts b/services/content-watcher/libs/common/src/pubsub/announcers/update.ts new file mode 100644 index 00000000..682d2ef9 --- /dev/null +++ b/services/content-watcher/libs/common/src/pubsub/announcers/update.ts @@ -0,0 +1,26 @@ +import { Processor } from '@nestjs/bullmq'; +import { Injectable } from '@nestjs/common'; +import { Job } from 'bullmq'; +import { QueueConstants } from '../..'; +import { BaseConsumer } from '../../utils/base-consumer'; +import { AnnouncementResponse } from '../../interfaces/announcement_response'; +import { PubSubService } from '../pubsub.service'; + +@Injectable() +@Processor(QueueConstants.UPDATE_QUEUE_NAME, { concurrency: 2 }) +export class UpdateSubscriber extends BaseConsumer { + constructor(private readonly pubsubService: PubSubService) { + super(); + } + + async process(job: Job): Promise { + this.logger.debug(`Sending ⏫ update to registered webhooks`); + try { + await this.pubsubService.process(job.data, 'update'); + this.logger.debug(`Update sent to registered webhooks`); + } catch (error) { + this.logger.error(`Failed to send update to registered webhooks`); + throw error; + } + } +} diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts new file mode 100644 index 00000000..731149b2 --- /dev/null +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts @@ -0,0 +1,148 @@ +import { Module } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { BullModule } from '@nestjs/bullmq'; +import { ScheduleModule } from '@nestjs/schedule'; +import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { BullBoardModule } from '@bull-board/nestjs'; +import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; +import { ExpressAdapter } from '@bull-board/express'; +import { ConfigModule } from '../config/config.module'; +import { ConfigService } from '../config/config.service'; +import { QueueConstants } from '../utils/queues'; +import { PubSubService } from './pubsub.service'; +import { BroadcastSubscriber } from './announcers/broadcast'; +import { ProfileSubscriber } from './announcers/profile'; +import { ReactionSubscriber } from './announcers/reaction'; +import { ReplySubscriber } from './announcers/reply'; +import { TomstoneSubscriber } from './announcers/tombstone'; +import { UpdateSubscriber } from './announcers/update'; + +@Module({ + imports: [ + ConfigModule, + RedisModule.forRootAsync( + { + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + config: [{ url: configService.redisUrl.toString() }], + }), + inject: [ConfigService], + }, + true, // isGlobal + ), + BullModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + }; + }, + inject: [ConfigService], + }), + BullModule.registerQueue( + { + name: QueueConstants.BROADCAST_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.REPLY_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.REACTION_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.TOMBSTONE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.PROFILE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.UPDATE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + ), + EventEmitterModule.forRoot({ + // Use this instance throughout the application + global: true, + // set this to `true` to use wildcards + wildcard: false, + // the delimiter used to segment namespaces + delimiter: '.', + // set this to `true` if you want to emit the newListener event + newListener: false, + // set this to `true` if you want to emit the removeListener event + removeListener: false, + // the maximum amount of listeners that can be assigned to an event + maxListeners: 10, + // show event name in memory leak message when more than maximum amount of listeners is assigned + verboseMemoryLeak: false, + // disable throwing uncaughtException if an error event is emitted and it has no listeners + ignoreErrors: false, + }), + ScheduleModule.forRoot(), + ], + providers: [PubSubService, BroadcastSubscriber, ProfileSubscriber, ReactionSubscriber, ReplySubscriber, TomstoneSubscriber, UpdateSubscriber], + controllers: [], + exports: [PubSubService], +}) +export class PubSubModule {} diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts new file mode 100644 index 00000000..0fcff86e --- /dev/null +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts @@ -0,0 +1,57 @@ +import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { Injectable, Logger } from '@nestjs/common'; +import Redis from 'ioredis'; +import axios from 'axios'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Queue } from 'bullmq'; +import { REGISTERED_WEBHOOK_KEY } from '../constants'; +import { AnnouncementResponse } from '../interfaces/announcement_response'; +import { ConfigService } from '../config/config.service'; +import { QueueConstants } from '../utils/queues'; + +@Injectable() +export class PubSubService { + private logger: Logger; + + constructor( + @InjectRedis() private redis: Redis, + private readonly configService: ConfigService, + ) { + this.logger = new Logger(this.constructor.name); + } + + async process(message: AnnouncementResponse, messageType: string) { + this.logger.debug(`Sending announcements to webhooks`); + + // Get the registered webhooks for the specific messageType + const registeredWebhook = await this.redis.get(REGISTERED_WEBHOOK_KEY); + let currentWebhookRegistrationDtos: { announcementType: string; urls: string[] }[] = []; + + // Pause the queues since nobody is listening + if (!registeredWebhook) { + return; + } + + currentWebhookRegistrationDtos = JSON.parse(registeredWebhook); + // Find the registrations for the specified messageType + const registrationsForMessageType = currentWebhookRegistrationDtos.find((registration) => registration.announcementType === messageType.toLowerCase()); + + if (registrationsForMessageType) { + registrationsForMessageType.urls.forEach(async (webhookUrl) => { + let retries = 0; + while (retries < this.configService.getWebookMaxRetries()) { + try { + // eslint-disable-next-line no-await-in-loop + await axios.post(webhookUrl, message); + this.logger.debug(`Announcement sent to webhook: ${webhookUrl}`); + break; + } catch (error) { + this.logger.error(`Failed to send announcement to webhook: ${webhookUrl}`); + this.logger.error(error); + retries += 1; + } + } + }); + } + } +} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 6991b305..762a2025 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -13,7 +13,7 @@ import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; import { QueueConstants } from '../utils/queues'; -import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY } from '../constants'; +import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; @@ -83,7 +83,12 @@ export class ScannerService implements OnApplicationBootstrap { this.logger.log('Deferring next blockchain scan until queue is empty'); return; } + const registeredWebhook = await this.cache.get(REGISTERED_WEBHOOK_KEY); + if (!registeredWebhook) { + this.logger.log('No registered webhooks; no scan performed.'); + return; + } const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); const eventsToWatch: ChainWatchOptionsDto = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; From 8aeca5711cb03d41018dca6a0bf7e47803204e2d Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 19 Oct 2023 16:48:13 -0500 Subject: [PATCH 095/137] remove job once its sent it callbacks --- .../libs/common/src/ipfs/ipfs.module.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts index 98a27b00..056beb69 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts @@ -65,21 +65,69 @@ import { IpfsService } from '../utils/ipfs.client'; }, { name: QueueConstants.BROADCAST_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }, { name: QueueConstants.REACTION_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }, { name: QueueConstants.REPLY_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }, { name: QueueConstants.PROFILE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }, { name: QueueConstants.TOMBSTONE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }, { name: QueueConstants.UPDATE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, }, ), ], From 6d22bdc9dd045689dfe066a55d2b3387007174f1 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 23 Oct 2023 11:52:48 -0500 Subject: [PATCH 096/137] publish content hash as string --- .../libs/common/src/ipfs/ipfs.dsnp.ts | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index e26381c7..31f4c9a5 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -19,6 +19,7 @@ import { UpdateAnnouncement, } from '../interfaces/dsnp'; import { AnnouncementResponse } from '../interfaces/announcement_response'; +import { bases } from "multiformats/basics"; @Injectable() @Processor(QueueConstants.IPFS_QUEUE, { @@ -79,9 +80,15 @@ export class IPFSContentProcessor extends BaseConsumer { records.forEach(async (mapRecord) => { switch (mapRecord.announcementType) { case AnnouncementType.Broadcast: { + const recordAnnouncement = mapRecord as BroadcastAnnouncement; const broadCastResponse: AnnouncementResponse = { schemaId: jobData.schemaId, - announcement: mapRecord as BroadcastAnnouncement, + announcement: { + fromId: recordAnnouncement.fromId, + contentHash: bases.base58btc.encode(recordAnnouncement.contentHash as any), + url: recordAnnouncement.url, + announcementType: recordAnnouncement.announcementType, + }, requestId: jobRequestId, }; if (!(await this.isQueueFull(this.broadcastQueue))) { @@ -91,9 +98,15 @@ export class IPFSContentProcessor extends BaseConsumer { break; } case AnnouncementType.Tombstone: { + const tombRecord = mapRecord as TombstoneAnnouncement; const tombstoneResponse: AnnouncementResponse = { schemaId: jobData.schemaId, - announcement: mapRecord as TombstoneAnnouncement, + announcement: { + fromId: tombRecord.fromId, + targetAnnouncementType: tombRecord.targetAnnouncementType, + targetContentHash: bases.base58btc.encode(tombRecord.targetContentHash as any), + announcementType: tombRecord.announcementType, + }, requestId: jobRequestId, }; if (!(await this.isQueueFull(this.tombstoneQueue))) { @@ -103,9 +116,16 @@ export class IPFSContentProcessor extends BaseConsumer { break; } case AnnouncementType.Reaction: { + const reactionRecord = mapRecord as ReactionAnnouncement; const reactionResponse: AnnouncementResponse = { schemaId: jobData.schemaId, - announcement: mapRecord as ReactionAnnouncement, + announcement: { + fromId: reactionRecord.fromId, + announcementType: reactionRecord.announcementType, + inReplyTo:reactionRecord.inReplyTo, + emoji: reactionRecord.emoji, + apply: reactionRecord.apply, + }, requestId: jobRequestId, }; if (!(await this.isQueueFull(this.reactionQueue))) { @@ -115,9 +135,16 @@ export class IPFSContentProcessor extends BaseConsumer { break; } case AnnouncementType.Reply: { + const replyRecord = mapRecord as ReplyAnnouncement; const replyResponse: AnnouncementResponse = { schemaId: jobData.schemaId, - announcement: mapRecord as ReplyAnnouncement, + announcement:{ + fromId: replyRecord.fromId, + announcementType: replyRecord.announcementType, + url: replyRecord.url, + inReplyTo: replyRecord.inReplyTo, + contentHash: bases.base58btc.encode(replyRecord.contentHash as any), + }, requestId: jobRequestId, }; if (!(await this.isQueueFull(this.replyQueue))) { @@ -127,9 +154,15 @@ export class IPFSContentProcessor extends BaseConsumer { break; } case AnnouncementType.Profile: { + const profileRecord = mapRecord as ProfileAnnouncement; const profileResponse: AnnouncementResponse = { schemaId: jobData.schemaId, - announcement: mapRecord as ProfileAnnouncement, + announcement:{ + fromId: profileRecord.fromId, + announcementType: profileRecord.announcementType, + url: profileRecord.url, + contentHash: bases.base58btc.encode(profileRecord.contentHash as any), + }, requestId: jobRequestId, }; if (!(await this.isQueueFull(this.profileQueue))) { @@ -139,9 +172,17 @@ export class IPFSContentProcessor extends BaseConsumer { break; } case AnnouncementType.Update: { + const updateRecord = mapRecord as UpdateAnnouncement; const updateResponse: AnnouncementResponse = { schemaId: jobData.schemaId, - announcement: mapRecord as UpdateAnnouncement, + announcement: { + fromId: updateRecord.fromId, + announcementType: updateRecord.announcementType, + url: updateRecord.url, + contentHash: bases.base58btc.encode(updateRecord.contentHash as any), + targetAnnouncementType: updateRecord.targetAnnouncementType, + targetContentHash: bases.base58btc.encode(updateRecord.targetContentHash as any), + }, requestId: jobRequestId, }; if (!(await this.isQueueFull(this.profileQueue))) { From 83ce7b55aa82d1fb86026f8719eccb3da9077d4a Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Mon, 23 Oct 2023 16:33:54 -0500 Subject: [PATCH 097/137] post testing cleanups --- .../libs/common/src/dtos/subscription.webhook.dto.ts | 2 +- .../content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts | 9 ++++----- .../libs/common/src/pubsub/pubsub.service.ts | 5 ++--- services/content-watcher/swagger.yaml | 4 +++- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts b/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts index b6e53626..0a351559 100644 --- a/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts @@ -13,7 +13,7 @@ export class WebhookRegistrationDto { @IsArray() @ApiProperty({ description: 'Announcement types to send to the webhook', - example: ['Broadcast', 'Reaction'], + example: ['Broadcast', 'Reaction', 'Tombstone', 'Reply', 'Update'], }) announcementTypes: string[]; // Announcement types to send to the webhook } diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index 31f4c9a5..dffc5e08 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -3,13 +3,13 @@ import { Job, Queue } from 'bullmq'; import { InjectQueue, Processor } from '@nestjs/bullmq'; import { hexToString } from '@polkadot/util'; import parquet from '@dsnp/parquetjs'; +import { bases } from 'multiformats/basics'; import { ConfigService } from '../config/config.service'; import { QueueConstants, calculateJobId } from '..'; import { IIPFSJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; import { - Announcement, AnnouncementType, BroadcastAnnouncement, ProfileAnnouncement, @@ -19,7 +19,6 @@ import { UpdateAnnouncement, } from '../interfaces/dsnp'; import { AnnouncementResponse } from '../interfaces/announcement_response'; -import { bases } from "multiformats/basics"; @Injectable() @Processor(QueueConstants.IPFS_QUEUE, { @@ -122,7 +121,7 @@ export class IPFSContentProcessor extends BaseConsumer { announcement: { fromId: reactionRecord.fromId, announcementType: reactionRecord.announcementType, - inReplyTo:reactionRecord.inReplyTo, + inReplyTo: reactionRecord.inReplyTo, emoji: reactionRecord.emoji, apply: reactionRecord.apply, }, @@ -138,7 +137,7 @@ export class IPFSContentProcessor extends BaseConsumer { const replyRecord = mapRecord as ReplyAnnouncement; const replyResponse: AnnouncementResponse = { schemaId: jobData.schemaId, - announcement:{ + announcement: { fromId: replyRecord.fromId, announcementType: replyRecord.announcementType, url: replyRecord.url, @@ -157,7 +156,7 @@ export class IPFSContentProcessor extends BaseConsumer { const profileRecord = mapRecord as ProfileAnnouncement; const profileResponse: AnnouncementResponse = { schemaId: jobData.schemaId, - announcement:{ + announcement: { fromId: profileRecord.fromId, announcementType: profileRecord.announcementType, url: profileRecord.url, diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts index 0fcff86e..b42f1f03 100644 --- a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts @@ -2,12 +2,9 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Injectable, Logger } from '@nestjs/common'; import Redis from 'ioredis'; import axios from 'axios'; -import { InjectQueue } from '@nestjs/bullmq'; -import { Queue } from 'bullmq'; import { REGISTERED_WEBHOOK_KEY } from '../constants'; import { AnnouncementResponse } from '../interfaces/announcement_response'; import { ConfigService } from '../config/config.service'; -import { QueueConstants } from '../utils/queues'; @Injectable() export class PubSubService { @@ -41,6 +38,8 @@ export class PubSubService { let retries = 0; while (retries < this.configService.getWebookMaxRetries()) { try { + this.logger.debug(`Sending announcement to webhook: ${webhookUrl}`); + this.logger.debug(`Announcement: ${JSON.stringify(message)}`); // eslint-disable-next-line no-await-in-loop await axios.post(webhookUrl, message); this.logger.debug(`Announcement sent to webhook: ${webhookUrl}`); diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index 73e460b2..6b17e3c3 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -181,10 +181,12 @@ components: example: - Broadcast - Reaction + - Tombstone + - Reply + - Update type: array items: type: string required: - url - announcementTypes - responses: {} From 6b4bab24aebd96c774c5b712f4ddac542ac48b3a Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Tue, 24 Oct 2023 09:08:45 -0500 Subject: [PATCH 098/137] update swagger --- services/content-watcher/swagger.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml index 6b17e3c3..d68496a6 100644 --- a/services/content-watcher/swagger.yaml +++ b/services/content-watcher/swagger.yaml @@ -140,7 +140,9 @@ components: items: type: string dsnpIds: - description: Specific dsnpIds (msa_id) to watch for + description: Specific dsnpIds (msa_id) to watch for, these are the ids of the + publisher of the messages on frequency. If you want to watch for all messages + on frequency, leave this blank. example: - '10074' - '100001' From 6716bae793298e6a101317a7c1278eaf35cec2f7 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Wed, 25 Oct 2023 09:32:13 -0500 Subject: [PATCH 099/137] use setex for internal data --- services/content-watcher/apps/api/src/api.service.ts | 4 ++-- services/content-watcher/libs/common/src/scanner/scanner.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 4bdc7675..c3565a50 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -9,7 +9,7 @@ import { ScannerService } from '../../../libs/common/src/scanner/scanner'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../../../libs/common/src/constants'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; import { WebhookRegistrationDto } from '../../../libs/common/src/dtos/subscription.webhook.dto'; -import { AnnouncementType } from '../../../libs/common/src/interfaces/dsnp'; +import { RedisUtils } from '../../../libs/common/src/utils/redis'; @Injectable() export class ApiService { @@ -25,7 +25,7 @@ export class ApiService { public setLastSeenBlockNumber(blockNumber: bigint) { this.logger.warn(`Setting last seen block number to ${blockNumber}`); - return this.redis.set(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, blockNumber.toString()); + return this.redis.setex(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, blockNumber.toString()); } public async setWatchOptions(watchOptions: ChainWatchOptionsDto) { diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 762a2025..05ac0c54 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -16,6 +16,7 @@ import { QueueConstants } from '../utils/queues'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; +import { RedisUtils } from '../utils/redis'; @Injectable() export class ScannerService implements OnApplicationBootstrap { @@ -211,6 +212,6 @@ export class ScannerService implements OnApplicationBootstrap { } private async setLastSeenBlockNumber(b: bigint): Promise { - await this.cache.set(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, b.toString()); + await this.cache.setex(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, b.toString()); } } From d5f8d96ba6b869fbae46a47e3fad0212d06ae369 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Wed, 15 Nov 2023 04:50:59 -0600 Subject: [PATCH 100/137] add make file --- services/content-watcher/Makefile | 37 +++++++++++++++++++++++++++ services/content-watcher/package.json | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 services/content-watcher/Makefile diff --git a/services/content-watcher/Makefile b/services/content-watcher/Makefile new file mode 100644 index 00000000..2775c419 --- /dev/null +++ b/services/content-watcher/Makefile @@ -0,0 +1,37 @@ +###### +###### Build targets +###### + +.PHONY: build +build: build-watcher + +.PHONY: build-watcher +build-watcher: + @(npm run build) + +clean: clean-watcher + @cat /dev/null + +.PHONY: clean-watcher +clean-watcher: + @rm -rf dist + +###### +###### Running apps targets +###### + +.PHONY: start-watcher +start-watcher: + @(npm run docker-run:dev) + +.PHONY: stop-watcher +stop-watcher: + @(npm run docker-stop:dev) + +###### +###### Misc targets +###### + +.PHONY: lint +lint: + @(npm run lint ) diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 4613fd32..d026ceaa 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -14,7 +14,7 @@ "docker-build": "docker build -t content-watcher-service .", "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", "docker-run": " build -t content-watcher-service-deploy . ; docker run -p 6379:6379 --env-file .env content-watcher-service-deploy", - "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs -f content-watcher-service", + "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs", "docker-stop:dev": "docker-compose -f docker-compose.dev.yaml stop", "clean": "rm -Rf dist", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", From 3e6cda946bcfede4943ed42b8c3d5d238d857111 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Wed, 15 Nov 2023 18:04:48 +0530 Subject: [PATCH 101/137] some cleanups --- services/content-watcher/Makefile | 6 +- .../content-watcher/docker-compose.dev.yaml | 54 +++++---- .../content-watcher/scripts/local-init.cjs | 91 --------------- .../content-watcher/scripts/local-setup.sh | 109 ++++++++++++++++++ 4 files changed, 141 insertions(+), 119 deletions(-) delete mode 100644 services/content-watcher/scripts/local-init.cjs create mode 100644 services/content-watcher/scripts/local-setup.sh diff --git a/services/content-watcher/Makefile b/services/content-watcher/Makefile index 2775c419..f0d7880c 100644 --- a/services/content-watcher/Makefile +++ b/services/content-watcher/Makefile @@ -22,11 +22,7 @@ clean-watcher: .PHONY: start-watcher start-watcher: - @(npm run docker-run:dev) - -.PHONY: stop-watcher -stop-watcher: - @(npm run docker-stop:dev) + @(nest start api --watch) ###### ###### Misc targets diff --git a/services/content-watcher/docker-compose.dev.yaml b/services/content-watcher/docker-compose.dev.yaml index 73708a12..407846da 100644 --- a/services/content-watcher/docker-compose.dev.yaml +++ b/services/content-watcher/docker-compose.dev.yaml @@ -14,10 +14,38 @@ services: image: frequencychain/instant-seal-node:latest ports: - 9944:9944 + profiles: ['', 'instant'] networks: - content-watcher-service + container_name: frequency-node volumes: - - frequency_data:/data/frequency + - chainstorage:/data + + frequency-interval: + image: frequencychain/instant-seal-node:latest + command: --sealing=interval --sealing-interval 3 --sealing-create-empty-blocks + ports: + - 9944:9944 + profiles: + - 'interval' + networks: + - content-watcher-service + container_name: frequency-interval-node + volumes: + - chainstorage:/data + + frequency-manual: + image: frequencychain/instant-seal-node:latest + command: --sealing=manual + ports: + - 9944:9944 + profiles: + - 'manual' + networks: + - content-watcher-service + container_name: frequency-manual-node + volumes: + - chainstorage:/data kubo_ipfs: image: ipfs/kubo:latest @@ -30,31 +58,11 @@ services: volumes: - ipfs_data:/data/ipfs - content-watcher-service-api: - build: - context: . - dockerfile: dev.Dockerfile - ports: - - 3000:3000 - env_file: - - .env.docker.dev - environment: - - START_PROCESS=api - - REDIS_URL=redis://redis:6379 - volumes: - - ./:/app - depends_on: - - redis - - frequency - - kubo_ipfs - networks: - - content-watcher-service - volumes: redis_data: ipfs_data: - frequency_data: - + chainstorage: + external: false networks: content-watcher-service: diff --git a/services/content-watcher/scripts/local-init.cjs b/services/content-watcher/scripts/local-init.cjs deleted file mode 100644 index 2d518eff..00000000 --- a/services/content-watcher/scripts/local-init.cjs +++ /dev/null @@ -1,91 +0,0 @@ -const { options } = require("@frequency-chain/api-augment"); -const { WsProvider, ApiPromise, Keyring } = require("@polkadot/api"); -const { deploy } = require("@dsnp/frequency-schemas/cli/deploy"); - -// Given a list of events, a section and a method, -// returns the first event with matching section and method. -const eventWithSectionAndMethod = (events, section, method) => { - const evt = events.find(({ event }) => event.section === section && event.method === method); - return evt?.event; -}; - -const main = async () => { - console.log("A quick script that will setup a clean localhost instance of Frequency for DSNP "); - - const providerUri = "ws://127.0.0.1:9944"; - const provider = new WsProvider(providerUri); - const api = await ApiPromise.create({ provider, throwOnConnect: true, ...options }); - const keys = new Keyring().addFromUri("//Alice", {}, "sr25519"); - - // Create alice msa - await new Promise((resolve, reject) => { - console.log("Creating an MSA..."); - api.tx.msa.create() - .signAndSend(keys, {}, ({ status, events, dispatchError }) => { - if (dispatchError) { - console.error("ERROR: ", dispatchError.toHuman()); - reject(); - } else if (status.isInBlock || status.isFinalized) { - const evt = eventWithSectionAndMethod(events, "msa", "MsaCreated"); - if (evt) { - const id = evt?.data[0]; - console.log("SUCCESS: MSA Created:" + id); - resolve(); - } else { - console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); - reject(); - } - } - }); - }); - - // Create alice provider - await new Promise((resolve, reject) => { - console.log("Creating an Provider..."); - api.tx.msa.createProvider("alice") - .signAndSend(keys, {}, ({ status, events, dispatchError }) => { - if (dispatchError) { - console.error("ERROR: ", dispatchError.toHuman()); - reject(); - } else if (status.isInBlock || status.isFinalized) { - const evt = eventWithSectionAndMethod(events, "msa", "ProviderCreated"); - if (evt) { - const id = evt?.data[0]; - console.log("SUCCESS: Provider Created:" + id); - resolve(); - } else { - console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); - reject(); - } - } - }); - }); - - // Alice provider get Capacity - await new Promise((resolve, reject) => { - console.log("Staking for Capacity..."); - api.tx.capacity.stake("1", 500_000 * Math.pow(8, 10)) - .signAndSend(keys, {}, ({ status, events, dispatchError }) => { - if (dispatchError) { - console.error("ERROR: ", dispatchError.toHuman()); - reject(); - } else if (status.isInBlock || status.isFinalized) { - const evt = eventWithSectionAndMethod(events, "capacity", "Staked"); - if (evt) { - console.log("SUCCESS: Provider Staked:", evt.data.toHuman()); - resolve(); - } else { - console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); - reject(); - } - } - }); - }); - - // Deploy Schemas - await deploy(); - - console.log("Setup Complete!"); -} - -main().catch(console.error).finally(process.exit); diff --git a/services/content-watcher/scripts/local-setup.sh b/services/content-watcher/scripts/local-setup.sh new file mode 100644 index 00000000..7e4b143d --- /dev/null +++ b/services/content-watcher/scripts/local-setup.sh @@ -0,0 +1,109 @@ +#!/bin/bash + +PROFILE=interval +PROJECT_NAME=cr +MODE=startup + +function help() { + cat << EOI +Usage: $( basename ${1} ) -i|-d [-s scenario] [-p profile-name] [-n project-name] [-h] + +Where: + -i initialize services + + -d delete services + + -p profile-name 'profile-name' is the name of a Docker profile from + docker-compose.dev.yaml. Options are: + 'instant' - Use an instant-seal Frequency node + 'interval' - Use an interval-seal Frequency node (default) + + -n project-name 'project-name' is the prefix that will be added to container, + volume, and network names in Docker. (default: 'cr') + +EOI +} + +while getopts "hp:n:ids:" OPTION +do + case ${OPTION} in + + "h") help $0 + exit 0 + ;; + + "n") PROJECT_NAME="${OPTARG}" + ;; + + "p") if [ "${OPTARG}" = "''" ] + then + PROFILE= + else + PROFILE="${OPTARG}" + fi + ;; + + "i") MODE=startup + ;; + + "d") MODE=teardown + ;; + + "?") help $0 + exit 1 + ;; + + esac +done + +if [ -n "${PROFILE}" ] +then + if [[ ${PROFILE} = "interval" || ${PROFILE} = "instant" ]] + then + PROFILE="--profile ${PROFILE}" + else + echo "Invalid profile specified: ${PROFILE}" + help $0 + exit 1 + fi +else + PROFILE="--profile interval" +fi + +export TOPDIR=$( dirname ${0} )/../.. + +function teardown() { + # Stop previously running containers + docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} down + + # Remove chain & db storage + docker volume rm ${PROJECT_NAME}_chainstorage 2>/dev/null + docker volume rm ${PROJECT_NAME}_dbstorage 2>/dev/null + + # Stop running services + pm2 delete ${TOPDIR}/tools/scripts/test-pm2.config.js +} + +function startup() { + # Start containers for chain & DB + docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} up -d + + # Set up chain scenario + ( cd ${TOPDIR}/tools/ci/setup ; npm ci ; npm run main ) + + ##### Start mock-service ##### + + # Make sure pm2 is installed + if ! which pm2 >| /dev/null + then + echo "Installing pm2" + npm i --global pm2 + fi +} + +if [ ${MODE} = "startup" ] +then + startup +else + teardown +fi From d1e6cb9d84e3a6b37e8b2dffeee93fbeb1f60ed2 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 16 Nov 2023 17:22:35 +0530 Subject: [PATCH 102/137] some setups --- services/content-watcher/.env.cp.docker.dev | 30 + services/content-watcher/Makefile | 12 + .../apps/api/test/app.e2e-spec.ts | 151 ++++ .../apps/api/test/jest-e2e.json | 9 + .../content-watcher/docker-compose.dev.yaml | 36 +- services/content-watcher/package-lock.json | 9 + services/content-watcher/package.json | 3 +- .../scripts/chain-setup/index..cjs | 91 +++ .../scripts/chain-setup/package-lock.json | 771 ++++++++++++++++++ .../scripts/chain-setup/package.json | 20 + .../scripts/content-setup/index.js | 99 +++ .../scripts/content-setup/package-lock.json | 106 +++ .../scripts/content-setup/package.json | 16 + services/content-watcher/scripts/local-cw.sh | 36 + .../content-watcher/scripts/local-setup.sh | 24 +- .../scripts/test-pm2.config.js | 11 + 16 files changed, 1411 insertions(+), 13 deletions(-) create mode 100644 services/content-watcher/.env.cp.docker.dev create mode 100644 services/content-watcher/apps/api/test/app.e2e-spec.ts create mode 100644 services/content-watcher/apps/api/test/jest-e2e.json create mode 100644 services/content-watcher/scripts/chain-setup/index..cjs create mode 100644 services/content-watcher/scripts/chain-setup/package-lock.json create mode 100644 services/content-watcher/scripts/chain-setup/package.json create mode 100644 services/content-watcher/scripts/content-setup/index.js create mode 100644 services/content-watcher/scripts/content-setup/package-lock.json create mode 100644 services/content-watcher/scripts/content-setup/package.json create mode 100755 services/content-watcher/scripts/local-cw.sh mode change 100644 => 100755 services/content-watcher/scripts/local-setup.sh create mode 100644 services/content-watcher/scripts/test-pm2.config.js diff --git a/services/content-watcher/.env.cp.docker.dev b/services/content-watcher/.env.cp.docker.dev new file mode 100644 index 00000000..b108bf1f --- /dev/null +++ b/services/content-watcher/.env.cp.docker.dev @@ -0,0 +1,30 @@ +# Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development +# IPFS_ENDPOINT="https://ipfs.infura.io:5001" +# IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" +# IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" +# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" +IPFS_ENDPOINT="http://kubo_ipfs:5001" +IPFS_BASIC_AUTH_USER="" +IPFS_BASIC_AUTH_SECRET="" +IPFS_GATEWAY_URL="http://kubo_ipfs:8080/ipfs/[CID]" + +FREQUENCY_URL=ws://frequency:9944 +PROVIDER_ID=1 +REDIS_URL=redis://redis:6379 +BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 +QUEUE_HIGH_WATER=1000 +PROVIDER_ACCOUNT_SEED_PHRASE="//Alice" +WEBHOOK_FAILURE_THRESHOLD=3 +HEALTH_CHECK_SUCCESS_THRESHOLD=10 +WEBHOOK_RETRY_INTERVAL_SECONDS=10 +HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 +HEALTH_CHECK_MAX_RETRIES=4 +CAPACITY_LIMIT='{"type":"percentage", "value":80}' +ENVIRONMENT="dev" +API_PORT=3001 + +FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 +ASSET_EXPIRATION_INTERVAL_SECONDS=300 +BATCH_INTERVAL_SECONDS=12 +BATCH_MAX_COUNT=1000 +ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 diff --git a/services/content-watcher/Makefile b/services/content-watcher/Makefile index f0d7880c..a23d953e 100644 --- a/services/content-watcher/Makefile +++ b/services/content-watcher/Makefile @@ -24,6 +24,18 @@ clean-watcher: start-watcher: @(nest start api --watch) +###### +###### Testing targets +###### + +.PHONY: test-services-start +test-services-start: + @scripts/local-setup.sh -n cw-e2e -i + +.PHONY: test-services-stop +test-services-stop: + @scripts/local-setup.sh -n cw-e2e -d + ###### ###### Misc targets ###### diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts new file mode 100644 index 00000000..6caa8f36 --- /dev/null +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -0,0 +1,151 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable no-undef */ +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import request from 'supertest'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { ApiModule } from '../src/api.module'; + +describe('Content Publishing E2E request verification!', () => { + let app: INestApplication; + let module: TestingModule; + // eslint-disable-next-line no-promise-executor-return + const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + const validLocation = { + name: 'name of location', + accuracy: 97, + altitude: 10, + latitude: 37.26, + longitude: -119.59, + radius: 10, + units: 'm', + }; + const validTags = [ + { + type: 'mention', + mentionedId: 'dsnp://78187493520', + }, + { + type: 'hashtag', + name: '#taggedUser', + }, + ]; + const validContentNoUploadedAssets = { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + name: 'name of note content', + assets: [ + { + type: 'link', + name: 'link asset', + href: 'http://example.com', + }, + ], + tag: validTags, + location: validLocation, + }; + const validBroadCastNoUploadedAssets = { + content: validContentNoUploadedAssets, + }; + const validReplyNoUploadedAssets = { + content: validContentNoUploadedAssets, + inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }; + const validReaction = { + emoji: '🤌🏼', + apply: 5, + inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }; + + beforeEach(async () => { + module = await Test.createTestingModule({ + imports: [ApiModule], + }).compile(); + + app = module.createNestApplication(); + const eventEmitter = app.get(EventEmitter2); + eventEmitter.on('shutdown', async () => { + await app.close(); + }); + app.useGlobalPipes(new ValidationPipe()); + app.enableShutdownHooks(); + await app.init(); + }); + + it('(GET) /api/health', () => request(app.getHttpServer()).get('/api/health').expect(200).expect({ status: 200 })); + + describe('Validate Route params', () => { + it('invalid userDsnpId should fail', async () => { + const invalidDsnpUserId = '2gsjhdaj'; + return request(app.getHttpServer()) + .post(`/api/content/${invalidDsnpUserId}/broadcast`) + .send(validBroadCastNoUploadedAssets) + .expect(400) + .expect((res) => expect(res.text).toContain('must be a number string')); + }); + }); + + describe('(POST) /api/content/:dsnpUserId/broadcast', () => { + it('valid request without uploaded assets should work!', () => + request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(validBroadCastNoUploadedAssets) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + + it('valid broadcast request with uploaded assets should work!', async () => { + const file = Buffer.from('g'.repeat(30 * 1000 * 1000)); // 30MB + const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); + await sleep(1000); + const validBroadCastWithUploadedAssets = { + content: { + ...validContentNoUploadedAssets, + assets: [ + { + type: 'image', + name: 'image asset', + references: [ + { + referenceId: response.body.assetIds[0], + height: 123, + width: 321, + }, + ], + }, + ], + }, + }; + return request(app.getHttpServer()) + .post(`/api/content/123/broadcast`) + .send(validBroadCastWithUploadedAssets) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId')); + }, 15000); + }); + + describe('(POST) /api/content/:dsnpUserId/reply', () => { + it('valid request without assets should work!', () => + request(app.getHttpServer()) + .post(`/api/content/123/reply`) + .send(validReplyNoUploadedAssets) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + }); + + describe('(POST) /api/content/:dsnpUserId/reaction', () => { + it('valid request should work!', () => + request(app.getHttpServer()) + .post(`/api/content/123/reaction`) + .send(validReaction) + .expect(202) + .expect((res) => expect(res.text).toContain('referenceId'))); + }); + + afterEach(async () => { + try { + await app.close(); + } catch (err) { + console.error(err); + } + }, 15000); +}); diff --git a/services/content-watcher/apps/api/test/jest-e2e.json b/services/content-watcher/apps/api/test/jest-e2e.json new file mode 100644 index 00000000..e9d912f3 --- /dev/null +++ b/services/content-watcher/apps/api/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} diff --git a/services/content-watcher/docker-compose.dev.yaml b/services/content-watcher/docker-compose.dev.yaml index 407846da..7e3da724 100644 --- a/services/content-watcher/docker-compose.dev.yaml +++ b/services/content-watcher/docker-compose.dev.yaml @@ -17,7 +17,7 @@ services: profiles: ['', 'instant'] networks: - content-watcher-service - container_name: frequency-node + container_name: frequency volumes: - chainstorage:/data @@ -58,6 +58,40 @@ services: volumes: - ipfs_data:/data/ipfs + content-publishing-service-api: + image: amplicalabs/content-publishing-service:api-0.0.2-rc4 + ports: + - 3001:3001 + env_file: + - .env.cp.docker.dev + environment: + - START_PROCESS=api + - REDIS_URL=redis://redis:6379 + volumes: + - ./:/app + depends_on: + - redis + - frequency + - kubo_ipfs + networks: + - content-watcher-service + + content-publishing-service-worker: + image: amplicalabs/content-publishing-service:api-0.0.2-rc4 + env_file: + - .env.cp.docker.dev + environment: + - START_PROCESS=worker + - REDIS_URL=redis://redis:6379 + volumes: + - ./:/app + depends_on: + - redis + - frequency + - kubo_ipfs + networks: + - content-watcher-service + volumes: redis_data: ipfs_data: diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 0dcfd724..37f06a25 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -11056,6 +11056,15 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "node_modules/redoc-cli/node_modules/@types/mkdirp": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", + "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", + "extraneous": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/redoc-cli/node_modules/@types/node": { "version": "15.12.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index d026ceaa..5c06daa5 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -21,7 +21,8 @@ "pretest": "cp env.template .env", "test": "jest --coverage --verbose", "test:e2e": "set -a ; . .env ; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", - "local:init": "node scripts/local-init.cjs" + "local:init": "cd scripts/chain-setup && npm i && npm run main", + "local:publish": "cd scripts/content-setup && npm i && npm run main" }, "repository": { "type": "git", diff --git a/services/content-watcher/scripts/chain-setup/index..cjs b/services/content-watcher/scripts/chain-setup/index..cjs new file mode 100644 index 00000000..2d518eff --- /dev/null +++ b/services/content-watcher/scripts/chain-setup/index..cjs @@ -0,0 +1,91 @@ +const { options } = require("@frequency-chain/api-augment"); +const { WsProvider, ApiPromise, Keyring } = require("@polkadot/api"); +const { deploy } = require("@dsnp/frequency-schemas/cli/deploy"); + +// Given a list of events, a section and a method, +// returns the first event with matching section and method. +const eventWithSectionAndMethod = (events, section, method) => { + const evt = events.find(({ event }) => event.section === section && event.method === method); + return evt?.event; +}; + +const main = async () => { + console.log("A quick script that will setup a clean localhost instance of Frequency for DSNP "); + + const providerUri = "ws://127.0.0.1:9944"; + const provider = new WsProvider(providerUri); + const api = await ApiPromise.create({ provider, throwOnConnect: true, ...options }); + const keys = new Keyring().addFromUri("//Alice", {}, "sr25519"); + + // Create alice msa + await new Promise((resolve, reject) => { + console.log("Creating an MSA..."); + api.tx.msa.create() + .signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error("ERROR: ", dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, "msa", "MsaCreated"); + if (evt) { + const id = evt?.data[0]; + console.log("SUCCESS: MSA Created:" + id); + resolve(); + } else { + console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); + reject(); + } + } + }); + }); + + // Create alice provider + await new Promise((resolve, reject) => { + console.log("Creating an Provider..."); + api.tx.msa.createProvider("alice") + .signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error("ERROR: ", dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, "msa", "ProviderCreated"); + if (evt) { + const id = evt?.data[0]; + console.log("SUCCESS: Provider Created:" + id); + resolve(); + } else { + console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); + reject(); + } + } + }); + }); + + // Alice provider get Capacity + await new Promise((resolve, reject) => { + console.log("Staking for Capacity..."); + api.tx.capacity.stake("1", 500_000 * Math.pow(8, 10)) + .signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error("ERROR: ", dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, "capacity", "Staked"); + if (evt) { + console.log("SUCCESS: Provider Staked:", evt.data.toHuman()); + resolve(); + } else { + console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); + reject(); + } + } + }); + }); + + // Deploy Schemas + await deploy(); + + console.log("Setup Complete!"); +} + +main().catch(console.error).finally(process.exit); diff --git a/services/content-watcher/scripts/chain-setup/package-lock.json b/services/content-watcher/scripts/chain-setup/package-lock.json new file mode 100644 index 00000000..55a6d7e0 --- /dev/null +++ b/services/content-watcher/scripts/chain-setup/package-lock.json @@ -0,0 +1,771 @@ +{ + "name": "chain-setup", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "chain-setup", + "version": "1.0.0", + "license": "UNLICENSED", + "dependencies": { + "@frequency-chain/api-augment": "1.7.0", + "@polkadot/api": "^10.9.1", + "@polkadot/keyring": "^12.4.2", + "@polkadot/util": "^12.4.2" + } + }, + "node_modules/@frequency-chain/api-augment": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@frequency-chain/api-augment/-/api-augment-1.7.0.tgz", + "integrity": "sha512-o1G4vepM2a6eTqdBf+5A92/UsCrawc6L9lrPgwYQN4Ev3Pq4xWt2VPy1/OuKkH/E2ZYhI+IAlIOfHtu5O6Dkcw==", + "dependencies": { + "@polkadot/api": "^10.7.3", + "@polkadot/rpc-provider": "^10.7.3", + "@polkadot/types": "^10.7.3" + } + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@polkadot/api": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-10.10.1.tgz", + "integrity": "sha512-YHVkmNvjGF4Eg3thAbVhj9UX3SXx+Yxk6yVuzsEcckEudIRHzL2ikIWGCfUprfzSeFNpUCKdJIi1tsxVHtA7Tg==", + "dependencies": { + "@polkadot/api-augment": "10.10.1", + "@polkadot/api-base": "10.10.1", + "@polkadot/api-derive": "10.10.1", + "@polkadot/keyring": "^12.5.1", + "@polkadot/rpc-augment": "10.10.1", + "@polkadot/rpc-core": "10.10.1", + "@polkadot/rpc-provider": "10.10.1", + "@polkadot/types": "10.10.1", + "@polkadot/types-augment": "10.10.1", + "@polkadot/types-codec": "10.10.1", + "@polkadot/types-create": "10.10.1", + "@polkadot/types-known": "10.10.1", + "@polkadot/util": "^12.5.1", + "@polkadot/util-crypto": "^12.5.1", + "eventemitter3": "^5.0.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/api-augment": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-10.10.1.tgz", + "integrity": "sha512-J0r1DT1M5y75iO1iwcpUBokKD3q6b22kWlPfiHEDNFydVw5vm7OTRBk9Njjl8rOnlSzcW/Ya8qWfV/wkrqHxUQ==", + "dependencies": { + "@polkadot/api-base": "10.10.1", + "@polkadot/rpc-augment": "10.10.1", + "@polkadot/types": "10.10.1", + "@polkadot/types-augment": "10.10.1", + "@polkadot/types-codec": "10.10.1", + "@polkadot/util": "^12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/api-base": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-10.10.1.tgz", + "integrity": "sha512-joH2Ywxnn+AStkw+JWAdF3i3WJy4NcBYp0SWJM/WqGafWR/FuHnati2pcj/MHzkHT8JkBippmSSJFvsqRhlwcQ==", + "dependencies": { + "@polkadot/rpc-core": "10.10.1", + "@polkadot/types": "10.10.1", + "@polkadot/util": "^12.5.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/api-derive": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-10.10.1.tgz", + "integrity": "sha512-Q9Ibs4eRPqdV8qnRzFPD3dlWNbLHxRqMqNTNPmNQwKPo5m6fcQbZ0UZy3yJ+PI9S4AQHGhsWtfoi5qW8006GHQ==", + "dependencies": { + "@polkadot/api": "10.10.1", + "@polkadot/api-augment": "10.10.1", + "@polkadot/api-base": "10.10.1", + "@polkadot/rpc-core": "10.10.1", + "@polkadot/types": "10.10.1", + "@polkadot/types-codec": "10.10.1", + "@polkadot/util": "^12.5.1", + "@polkadot/util-crypto": "^12.5.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/keyring": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-12.5.1.tgz", + "integrity": "sha512-u6b+Q7wI6WY/vwmJS9uUHy/5hKZ226nTlVNmxjkj9GvrRsQvUSwS94163yHPJwiZJiIv5xK5m0rwCMyoYu+wjA==", + "dependencies": { + "@polkadot/util": "12.5.1", + "@polkadot/util-crypto": "12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "12.5.1", + "@polkadot/util-crypto": "12.5.1" + } + }, + "node_modules/@polkadot/networks": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-12.5.1.tgz", + "integrity": "sha512-PP6UUdzz6iHHZH4q96cUEhTcydHj16+61sqeaYEJSF6Q9iY+5WVWQ26+rdjmre/EBdrMQkSS/CKy73mO5z/JkQ==", + "dependencies": { + "@polkadot/util": "12.5.1", + "@substrate/ss58-registry": "^1.43.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/rpc-augment": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-10.10.1.tgz", + "integrity": "sha512-PcvsX8DNV8BNDXXnY2K8F4mE7cWz7fKg8ykXNZTN8XUN6MrI4k/ohv7itYic7X5LaP25ZmQt5UiGyjKDGIELow==", + "dependencies": { + "@polkadot/rpc-core": "10.10.1", + "@polkadot/types": "10.10.1", + "@polkadot/types-codec": "10.10.1", + "@polkadot/util": "^12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/rpc-core": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-10.10.1.tgz", + "integrity": "sha512-awfFfJYsVF6W4DrqTj5RP00SSDRNB770FIoe1QE1Op4NcSrfeLpwh54HUJS716f4l5mOSYuvMp+zCbKzt8zKow==", + "dependencies": { + "@polkadot/rpc-augment": "10.10.1", + "@polkadot/rpc-provider": "10.10.1", + "@polkadot/types": "10.10.1", + "@polkadot/util": "^12.5.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/rpc-provider": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-10.10.1.tgz", + "integrity": "sha512-VMDWoJgx6/mPHAOT66Sq+Jf2lJABfV/ZUIXtT2k8HjOndbm6oKrFqGEOSSLvB2q4olDee3FkFFxkyW1s6k4JaQ==", + "dependencies": { + "@polkadot/keyring": "^12.5.1", + "@polkadot/types": "10.10.1", + "@polkadot/types-support": "10.10.1", + "@polkadot/util": "^12.5.1", + "@polkadot/util-crypto": "^12.5.1", + "@polkadot/x-fetch": "^12.5.1", + "@polkadot/x-global": "^12.5.1", + "@polkadot/x-ws": "^12.5.1", + "eventemitter3": "^5.0.1", + "mock-socket": "^9.3.1", + "nock": "^13.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@substrate/connect": "0.7.33" + } + }, + "node_modules/@polkadot/types": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-10.10.1.tgz", + "integrity": "sha512-Ben62P1tjYEhKag34GBGcLX6NqcFR1VD5nNbWaxgr+t36Jl/tlHs6P9DlbFqQP7Tt9FmGrAYY0m3oTkhjG1NzA==", + "dependencies": { + "@polkadot/keyring": "^12.5.1", + "@polkadot/types-augment": "10.10.1", + "@polkadot/types-codec": "10.10.1", + "@polkadot/types-create": "10.10.1", + "@polkadot/util": "^12.5.1", + "@polkadot/util-crypto": "^12.5.1", + "rxjs": "^7.8.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-augment": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-10.10.1.tgz", + "integrity": "sha512-XRHE75IocXfFE6EADYov3pqXCyBk5SWbiHoZ0+4WYWP9SwMuzsBaAy84NlhLBlkG3+ehIqi0HpAd/qrljJGZbg==", + "dependencies": { + "@polkadot/types": "10.10.1", + "@polkadot/types-codec": "10.10.1", + "@polkadot/util": "^12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-codec": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-10.10.1.tgz", + "integrity": "sha512-ETPG0wzWzt/bDKRQmYbO7CLe/0lUt8VrG6/bECdv+Kye+8Qedba2LZyTWm/9f2ngms8TZ82yI8mPv/mozdtfnw==", + "dependencies": { + "@polkadot/util": "^12.5.1", + "@polkadot/x-bigint": "^12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-create": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-10.10.1.tgz", + "integrity": "sha512-7OiLzd+Ter5zrpjP7fDwA1m89kd38VvMVixfOSv8x7ld2pDT+yyyKl14TCwRSWrKWCMtIb6M3iasPhq5cUa7cw==", + "dependencies": { + "@polkadot/types-codec": "10.10.1", + "@polkadot/util": "^12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-known": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-10.10.1.tgz", + "integrity": "sha512-yRa1lbDRqg3V/zoa0vSwdGOiYTIWktILW8OfkaLDExTu0GZBSbVHZlLAta52XVpA9Zww7mrUUC9+iernOwk//w==", + "dependencies": { + "@polkadot/networks": "^12.5.1", + "@polkadot/types": "10.10.1", + "@polkadot/types-codec": "10.10.1", + "@polkadot/types-create": "10.10.1", + "@polkadot/util": "^12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/types-support": { + "version": "10.10.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-10.10.1.tgz", + "integrity": "sha512-Cd2mwk9RG6LlX8X3H0bRY7wCTbZPqU3z38CMFhvNkFDAyjqKjtn8hpS4n8mMrZK2EwCs/MjQH1wb7rtFkaWmJw==", + "dependencies": { + "@polkadot/util": "^12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/util": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-12.5.1.tgz", + "integrity": "sha512-fDBZL7D4/baMG09Qowseo884m3QBzErGkRWNBId1UjWR99kyex+cIY9fOSzmuQxo6nLdJlLHw1Nz2caN3+Bq0A==", + "dependencies": { + "@polkadot/x-bigint": "12.5.1", + "@polkadot/x-global": "12.5.1", + "@polkadot/x-textdecoder": "12.5.1", + "@polkadot/x-textencoder": "12.5.1", + "@types/bn.js": "^5.1.1", + "bn.js": "^5.2.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/util-crypto": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-12.5.1.tgz", + "integrity": "sha512-Y8ORbMcsM/VOqSG3DgqutRGQ8XXK+X9M3C8oOEI2Tji65ZsXbh9Yh+ryPLM0oBp/9vqOXjkLgZJbbVuQceOw0A==", + "dependencies": { + "@noble/curves": "^1.2.0", + "@noble/hashes": "^1.3.2", + "@polkadot/networks": "12.5.1", + "@polkadot/util": "12.5.1", + "@polkadot/wasm-crypto": "^7.2.2", + "@polkadot/wasm-util": "^7.2.2", + "@polkadot/x-bigint": "12.5.1", + "@polkadot/x-randomvalues": "12.5.1", + "@scure/base": "^1.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "12.5.1" + } + }, + "node_modules/@polkadot/wasm-bridge": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.2.2.tgz", + "integrity": "sha512-CgNENd65DVYtackOVXXRA0D1RPoCv5+77IdBCf7kNqu6LeAnR4nfTI6qjaApUdN1xRweUsQjSH7tu7VjkMOA0A==", + "dependencies": { + "@polkadot/wasm-util": "7.2.2", + "tslib": "^2.6.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.2.2.tgz", + "integrity": "sha512-1ZY1rxUTawYm0m1zylvBMFovNIHYgG2v/XoASNp/EMG5c8FQIxCbhJRaTBA983GVq4lN/IAKREKEp9ZbLLqssA==", + "dependencies": { + "@polkadot/wasm-bridge": "7.2.2", + "@polkadot/wasm-crypto-asmjs": "7.2.2", + "@polkadot/wasm-crypto-init": "7.2.2", + "@polkadot/wasm-crypto-wasm": "7.2.2", + "@polkadot/wasm-util": "7.2.2", + "tslib": "^2.6.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-asmjs": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.2.2.tgz", + "integrity": "sha512-wKg+cpsWQCTSVhjlHuNeB/184rxKqY3vaklacbLOMbUXieIfuDBav5PJdzS3yeiVE60TpYaHW4iX/5OYHS82gg==", + "dependencies": { + "tslib": "^2.6.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-init": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.2.2.tgz", + "integrity": "sha512-vD4iPIp9x+SssUIWUenxWLPw4BVIwhXHNMpsV81egK990tvpyIxL205/EF5QRb1mKn8WfWcNFm5tYwwh9NdnnA==", + "dependencies": { + "@polkadot/wasm-bridge": "7.2.2", + "@polkadot/wasm-crypto-asmjs": "7.2.2", + "@polkadot/wasm-crypto-wasm": "7.2.2", + "@polkadot/wasm-util": "7.2.2", + "tslib": "^2.6.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" + } + }, + "node_modules/@polkadot/wasm-crypto-wasm": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.2.2.tgz", + "integrity": "sha512-3efoIB6jA3Hhv6k0YIBwCtlC8gCSWCk+R296yIXRLLr3cGN415KM/PO/d1JIXYI64lbrRzWRmZRhllw3jf6Atg==", + "dependencies": { + "@polkadot/wasm-util": "7.2.2", + "tslib": "^2.6.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/wasm-util": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.2.2.tgz", + "integrity": "sha512-N/25960ifCc56sBlJZ2h5UBpEPvxBmMLgwYsl7CUuT+ea2LuJW9Xh8VHDN/guYXwmm92/KvuendYkEUykpm/JQ==", + "dependencies": { + "tslib": "^2.6.1" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "*" + } + }, + "node_modules/@polkadot/x-bigint": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-12.5.1.tgz", + "integrity": "sha512-Fw39eoN9v0sqxSzfSC5awaDVdzojIiE7d1hRSQgVSrES+8whWvtbYMR0qwbVhTuW7DvogHmye41P9xKMlXZysg==", + "dependencies": { + "@polkadot/x-global": "12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-fetch": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-12.5.1.tgz", + "integrity": "sha512-Bc019lOKCoQJrthiS+H3LwCahGtl5tNnb2HK7xe3DBQIUx9r2HsF/uEngNfMRUFkUYg5TPCLFbEWU8NIREBS1A==", + "dependencies": { + "@polkadot/x-global": "12.5.1", + "node-fetch": "^3.3.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-global": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-12.5.1.tgz", + "integrity": "sha512-6K0YtWEg0eXInDOihU5aSzeb1t9TiDdX9ZuRly+58ALSqw5kPZYmQLbzE1d8HWzyXRXK+YH65GtLzfMGqfYHmw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-randomvalues": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-12.5.1.tgz", + "integrity": "sha512-UsMb1d+77EPNjW78BpHjZLIm4TaIpfqq89OhZP/6gDIoS2V9iE/AK3jOWKm1G7Y2F8XIoX1qzQpuMakjfagFoQ==", + "dependencies": { + "@polkadot/x-global": "12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "@polkadot/util": "12.5.1", + "@polkadot/wasm-util": "*" + } + }, + "node_modules/@polkadot/x-textdecoder": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-12.5.1.tgz", + "integrity": "sha512-j2YZGWfwhMC8nHW3BXq10fAPY02ObLL/qoTjCMJ1Cmc/OGq18Ep7k9cXXbjFAq3wf3tUUewt/u/hStKCk3IvfQ==", + "dependencies": { + "@polkadot/x-global": "12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-textencoder": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-12.5.1.tgz", + "integrity": "sha512-1JNNpOGb4wD+c7zFuOqjibl49LPnHNr4rj4s3WflLUIZvOMY6euoDuN3ISjQSHCLlVSoH0sOCWA3qXZU4bCTDQ==", + "dependencies": { + "@polkadot/x-global": "12.5.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@polkadot/x-ws": { + "version": "12.5.1", + "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-12.5.1.tgz", + "integrity": "sha512-efNMhB3Lh6pW2iTipMkqwrjpuUtb3EwR/jYZftiIGo5tDPB7rqoMOp9s6KRFJEIUfZkLnMUtbkZ5fHzUJaCjmQ==", + "dependencies": { + "@polkadot/x-global": "12.5.1", + "tslib": "^2.6.2", + "ws": "^8.14.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@scure/base": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", + "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@substrate/connect": { + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.7.33.tgz", + "integrity": "sha512-1B984/bmXVQvTT9oV3c3b7215lvWmulP9rfP3T3Ri+OU3uIsyCzYw0A+XG6J8/jgO2FnroeNIBWlgoLaUM1uzw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@substrate/connect-extension-protocol": "^1.0.1", + "smoldot": "2.0.1" + } + }, + "node_modules/@substrate/connect-extension-protocol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz", + "integrity": "sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg==", + "optional": true + }, + "node_modules/@substrate/ss58-registry": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.44.0.tgz", + "integrity": "sha512-7lQ/7mMCzVNSEfDS4BCqnRnKCFKpcOaPrxMeGTXHX1YQzM/m2BBHjbK2C3dJvjv7GYxMiaTq/HdWQj1xS6ss+A==" + }, + "node_modules/@types/bn.js": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/mock-socket": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nock": { + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.8.tgz", + "integrity": "sha512-96yVFal0c/W1lG7mmfRe7eO+hovrhJYd2obzzOZ90f6fjpeU/XNvd9cYHZKZAQJumDfhXgoTpkpJ9pvMj+hqHw==", + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "engines": { + "node": ">= 8" + } + }, + "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==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/smoldot": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.1.tgz", + "integrity": "sha512-Wqw2fL/sELQByLSeeTX1Z/d0H4McmphPMx8vh6UZS/bIIDx81oU7s/drmx2iL/ME36uk++YxpRuJey8/MOyfOA==", + "optional": true, + "dependencies": { + "ws": "^8.8.1" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "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 + } + } + } + } +} diff --git a/services/content-watcher/scripts/chain-setup/package.json b/services/content-watcher/scripts/chain-setup/package.json new file mode 100644 index 00000000..94821f7c --- /dev/null +++ b/services/content-watcher/scripts/chain-setup/package.json @@ -0,0 +1,20 @@ +{ + "name": "chain-setup", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "main": "node index.cjs", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "type": "commonjs", + "author": "", + "license": "UNLICENSED", + "dependencies": { + "@polkadot/api": "^10.9.1", + "@polkadot/keyring": "^12.4.2", + "@polkadot/util": "^12.4.2", + "@frequency-chain/api-augment": "1.7.0" + + } +} diff --git a/services/content-watcher/scripts/content-setup/index.js b/services/content-watcher/scripts/content-setup/index.js new file mode 100644 index 00000000..54bf99e0 --- /dev/null +++ b/services/content-watcher/scripts/content-setup/index.js @@ -0,0 +1,99 @@ + +import axios from 'axios'; + +const validLocation = { + name: 'name of location', + accuracy: 97, + altitude: 10, + latitude: 37.26, + longitude: -119.59, + radius: 10, + units: 'm', + }; + const validTags = [ + { + type: 'mention', + mentionedId: 'dsnp://78187493520', + }, + { + type: 'hashtag', + name: '#taggedUser', + }, + ]; + const validContentNoUploadedAssets = { + content: 'test broadcast message', + published: '1970-01-01T00:00:00+00:00', + name: 'name of note content', + assets: [ + { + type: 'link', + name: 'link asset', + href: 'http://example.com', + }, + ], + tag: validTags, + location: validLocation, + }; + const validBroadCastNoUploadedAssets = { + content: validContentNoUploadedAssets, + }; + const validReplyNoUploadedAssets = { + content: validContentNoUploadedAssets, + inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }; + const validReaction = { + emoji: '🤌🏼', + apply: 5, + inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', +}; + +const apiUrl = 'http://localhost:3001/api'; + +const postBroadcast = async (dsnpUserId, content) => { + try { + const response = await axios.post(`${apiUrl}/content/${dsnpUserId}/broadcast`, content); + return response.data; + } catch (error) { + console.error('Error posting broadcast:', error.message); + throw error; + } +}; + +const postReply = async (dsnpUserId, content) => { + try { + const response = await axios.post(`${apiUrl}/content/${dsnpUserId}/reply`, content); + return response.data; + } catch (error) { + console.error('Error posting reply:', error.message); + throw error; + } +}; + +const postReaction = async (dsnpUserId, reaction) => { + try { + const response = await axios.post(`${apiUrl}/content/${dsnpUserId}/reaction`, reaction); + return response.data; + } catch (error) { + console.error('Error posting reaction:', error.message); + throw error; + } +}; + +const main = async () => { + const dsnpUserId = '123'; // Replace with the desired user ID + + // Example: Post broadcast + const broadcastResponse = await postBroadcast(dsnpUserId, validBroadCastNoUploadedAssets); + console.log('Broadcast Response:', broadcastResponse); + + // Example: Post reply + const replyResponse = await postReply(dsnpUserId, validReplyNoUploadedAssets); + console.log('Reply Response:', replyResponse); + + // Example: Post reaction + const reactionResponse = await postReaction(dsnpUserId, validReaction); + console.log('Reaction Response:', reactionResponse); +}; + +// Run the main function +main(); diff --git a/services/content-watcher/scripts/content-setup/package-lock.json b/services/content-watcher/scripts/content-setup/package-lock.json new file mode 100644 index 00000000..ba987681 --- /dev/null +++ b/services/content-watcher/scripts/content-setup/package-lock.json @@ -0,0 +1,106 @@ +{ + "name": "content-setup", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "content-setup", + "version": "1.0.0", + "license": "UNLICENSED", + "dependencies": { + "axios": "^1.6.2" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "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==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==" + } + } +} diff --git a/services/content-watcher/scripts/content-setup/package.json b/services/content-watcher/scripts/content-setup/package.json new file mode 100644 index 00000000..f4eae48a --- /dev/null +++ b/services/content-watcher/scripts/content-setup/package.json @@ -0,0 +1,16 @@ +{ + "name": "content-setup", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "main": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "type": "module", + "author": "", + "license": "UNLICENSED", + "dependencies": { + "axios": "^1.6.2" + } +} diff --git a/services/content-watcher/scripts/local-cw.sh b/services/content-watcher/scripts/local-cw.sh new file mode 100755 index 00000000..c49d8844 --- /dev/null +++ b/services/content-watcher/scripts/local-cw.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +WATCH= +SILENT=--silent + +while getopts "dw" OPTION +do + case ${OPTION} in + + "d") SILENT= + ;; + + "w") WATCH="--watch" + ;; + + "?") exit 1 + ;; + + esac +done + +export TOPDIR=$( dirname $( dirname $( dirname $( readlink -f ${0} ) ) ) ) +pushd ${TOPDIR}/backend +npm ci +npm run build + +# Make sure the correct set of services is running +set -a +. .env.docker.dev +pm2 delete all +pm2 start --cwd ${TOPDIR} ${TOPDIR}/tools/scripts/test-pm2.config.js --only mock-service + +# Run tests +echo "Running content watcher integration tests" +npm run test:e2e -- ${WATCH} ${SILENT} +popd diff --git a/services/content-watcher/scripts/local-setup.sh b/services/content-watcher/scripts/local-setup.sh old mode 100644 new mode 100755 index 7e4b143d..60ec1431 --- a/services/content-watcher/scripts/local-setup.sh +++ b/services/content-watcher/scripts/local-setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -PROFILE=interval +PROFILE=instant PROJECT_NAME=cr MODE=startup @@ -15,11 +15,11 @@ Where: -p profile-name 'profile-name' is the name of a Docker profile from docker-compose.dev.yaml. Options are: - 'instant' - Use an instant-seal Frequency node - 'interval' - Use an interval-seal Frequency node (default) + 'instant' - Use an instant-seal Frequency node (default) + 'interval' - Use an interval-seal Frequency node -n project-name 'project-name' is the prefix that will be added to container, - volume, and network names in Docker. (default: 'cr') + volume, and network names in Docker. (default: 'cw') EOI } @@ -70,18 +70,19 @@ else PROFILE="--profile interval" fi -export TOPDIR=$( dirname ${0} )/../.. +export TOPDIR=$( dirname ${0} )/.. function teardown() { # Stop previously running containers docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} down - # Remove chain & db storage + # Remove chain, ipfs & redis volumes docker volume rm ${PROJECT_NAME}_chainstorage 2>/dev/null - docker volume rm ${PROJECT_NAME}_dbstorage 2>/dev/null + docker volume rm ${PROJECT_NAME}_redis_data 2>/dev/null + docker volume rm ${PROJECT_NAME}_ipfs_data 2>/dev/null # Stop running services - pm2 delete ${TOPDIR}/tools/scripts/test-pm2.config.js + pm2 delete ${TOPDIR}/scripts/test-pm2.config.js } function startup() { @@ -89,10 +90,11 @@ function startup() { docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} up -d # Set up chain scenario - ( cd ${TOPDIR}/tools/ci/setup ; npm ci ; npm run main ) - - ##### Start mock-service ##### + ( cd ${TOPDIR}/scripts/chain-setup && npm i && npm run main) + # publish some content + ( cd ${TOPDIR}/scripts/content-setup && npm i && npm run main) + # Make sure pm2 is installed if ! which pm2 >| /dev/null then diff --git a/services/content-watcher/scripts/test-pm2.config.js b/services/content-watcher/scripts/test-pm2.config.js new file mode 100644 index 00000000..ef70f4a0 --- /dev/null +++ b/services/content-watcher/scripts/test-pm2.config.js @@ -0,0 +1,11 @@ +module.exports = { + apps: [ + { + name: 'api', + script: 'dist/apps/api/main.js', + env: { + HTTP_PORT: 3000, + }, + }, + ], + }; \ No newline at end of file From 20576b8627a8184665022673b4a0759a79abc5c5 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 16 Nov 2023 17:59:29 +0530 Subject: [PATCH 103/137] set up scenarios --- services/content-watcher/package.json | 2 +- .../{index..cjs => local-chain-setup.cjs} | 0 .../scripts/chain-setup/package-lock.json | 771 ------------------ .../scripts/chain-setup/package.json | 20 - .../content-watcher/scripts/local-setup.sh | 12 +- 5 files changed, 9 insertions(+), 796 deletions(-) rename services/content-watcher/scripts/chain-setup/{index..cjs => local-chain-setup.cjs} (100%) delete mode 100644 services/content-watcher/scripts/chain-setup/package-lock.json delete mode 100644 services/content-watcher/scripts/chain-setup/package.json diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 5c06daa5..ecca90ff 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -21,7 +21,7 @@ "pretest": "cp env.template .env", "test": "jest --coverage --verbose", "test:e2e": "set -a ; . .env ; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", - "local:init": "cd scripts/chain-setup && npm i && npm run main", + "local:init": "node scripts/chain-setup/local-chain-setup.cjs", "local:publish": "cd scripts/content-setup && npm i && npm run main" }, "repository": { diff --git a/services/content-watcher/scripts/chain-setup/index..cjs b/services/content-watcher/scripts/chain-setup/local-chain-setup.cjs similarity index 100% rename from services/content-watcher/scripts/chain-setup/index..cjs rename to services/content-watcher/scripts/chain-setup/local-chain-setup.cjs diff --git a/services/content-watcher/scripts/chain-setup/package-lock.json b/services/content-watcher/scripts/chain-setup/package-lock.json deleted file mode 100644 index 55a6d7e0..00000000 --- a/services/content-watcher/scripts/chain-setup/package-lock.json +++ /dev/null @@ -1,771 +0,0 @@ -{ - "name": "chain-setup", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "chain-setup", - "version": "1.0.0", - "license": "UNLICENSED", - "dependencies": { - "@frequency-chain/api-augment": "1.7.0", - "@polkadot/api": "^10.9.1", - "@polkadot/keyring": "^12.4.2", - "@polkadot/util": "^12.4.2" - } - }, - "node_modules/@frequency-chain/api-augment": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@frequency-chain/api-augment/-/api-augment-1.7.0.tgz", - "integrity": "sha512-o1G4vepM2a6eTqdBf+5A92/UsCrawc6L9lrPgwYQN4Ev3Pq4xWt2VPy1/OuKkH/E2ZYhI+IAlIOfHtu5O6Dkcw==", - "dependencies": { - "@polkadot/api": "^10.7.3", - "@polkadot/rpc-provider": "^10.7.3", - "@polkadot/types": "^10.7.3" - } - }, - "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@polkadot/api": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-10.10.1.tgz", - "integrity": "sha512-YHVkmNvjGF4Eg3thAbVhj9UX3SXx+Yxk6yVuzsEcckEudIRHzL2ikIWGCfUprfzSeFNpUCKdJIi1tsxVHtA7Tg==", - "dependencies": { - "@polkadot/api-augment": "10.10.1", - "@polkadot/api-base": "10.10.1", - "@polkadot/api-derive": "10.10.1", - "@polkadot/keyring": "^12.5.1", - "@polkadot/rpc-augment": "10.10.1", - "@polkadot/rpc-core": "10.10.1", - "@polkadot/rpc-provider": "10.10.1", - "@polkadot/types": "10.10.1", - "@polkadot/types-augment": "10.10.1", - "@polkadot/types-codec": "10.10.1", - "@polkadot/types-create": "10.10.1", - "@polkadot/types-known": "10.10.1", - "@polkadot/util": "^12.5.1", - "@polkadot/util-crypto": "^12.5.1", - "eventemitter3": "^5.0.1", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/api-augment": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-10.10.1.tgz", - "integrity": "sha512-J0r1DT1M5y75iO1iwcpUBokKD3q6b22kWlPfiHEDNFydVw5vm7OTRBk9Njjl8rOnlSzcW/Ya8qWfV/wkrqHxUQ==", - "dependencies": { - "@polkadot/api-base": "10.10.1", - "@polkadot/rpc-augment": "10.10.1", - "@polkadot/types": "10.10.1", - "@polkadot/types-augment": "10.10.1", - "@polkadot/types-codec": "10.10.1", - "@polkadot/util": "^12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/api-base": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-10.10.1.tgz", - "integrity": "sha512-joH2Ywxnn+AStkw+JWAdF3i3WJy4NcBYp0SWJM/WqGafWR/FuHnati2pcj/MHzkHT8JkBippmSSJFvsqRhlwcQ==", - "dependencies": { - "@polkadot/rpc-core": "10.10.1", - "@polkadot/types": "10.10.1", - "@polkadot/util": "^12.5.1", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/api-derive": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-10.10.1.tgz", - "integrity": "sha512-Q9Ibs4eRPqdV8qnRzFPD3dlWNbLHxRqMqNTNPmNQwKPo5m6fcQbZ0UZy3yJ+PI9S4AQHGhsWtfoi5qW8006GHQ==", - "dependencies": { - "@polkadot/api": "10.10.1", - "@polkadot/api-augment": "10.10.1", - "@polkadot/api-base": "10.10.1", - "@polkadot/rpc-core": "10.10.1", - "@polkadot/types": "10.10.1", - "@polkadot/types-codec": "10.10.1", - "@polkadot/util": "^12.5.1", - "@polkadot/util-crypto": "^12.5.1", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/keyring": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-12.5.1.tgz", - "integrity": "sha512-u6b+Q7wI6WY/vwmJS9uUHy/5hKZ226nTlVNmxjkj9GvrRsQvUSwS94163yHPJwiZJiIv5xK5m0rwCMyoYu+wjA==", - "dependencies": { - "@polkadot/util": "12.5.1", - "@polkadot/util-crypto": "12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "12.5.1", - "@polkadot/util-crypto": "12.5.1" - } - }, - "node_modules/@polkadot/networks": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-12.5.1.tgz", - "integrity": "sha512-PP6UUdzz6iHHZH4q96cUEhTcydHj16+61sqeaYEJSF6Q9iY+5WVWQ26+rdjmre/EBdrMQkSS/CKy73mO5z/JkQ==", - "dependencies": { - "@polkadot/util": "12.5.1", - "@substrate/ss58-registry": "^1.43.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/rpc-augment": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-10.10.1.tgz", - "integrity": "sha512-PcvsX8DNV8BNDXXnY2K8F4mE7cWz7fKg8ykXNZTN8XUN6MrI4k/ohv7itYic7X5LaP25ZmQt5UiGyjKDGIELow==", - "dependencies": { - "@polkadot/rpc-core": "10.10.1", - "@polkadot/types": "10.10.1", - "@polkadot/types-codec": "10.10.1", - "@polkadot/util": "^12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/rpc-core": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-10.10.1.tgz", - "integrity": "sha512-awfFfJYsVF6W4DrqTj5RP00SSDRNB770FIoe1QE1Op4NcSrfeLpwh54HUJS716f4l5mOSYuvMp+zCbKzt8zKow==", - "dependencies": { - "@polkadot/rpc-augment": "10.10.1", - "@polkadot/rpc-provider": "10.10.1", - "@polkadot/types": "10.10.1", - "@polkadot/util": "^12.5.1", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/rpc-provider": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-10.10.1.tgz", - "integrity": "sha512-VMDWoJgx6/mPHAOT66Sq+Jf2lJABfV/ZUIXtT2k8HjOndbm6oKrFqGEOSSLvB2q4olDee3FkFFxkyW1s6k4JaQ==", - "dependencies": { - "@polkadot/keyring": "^12.5.1", - "@polkadot/types": "10.10.1", - "@polkadot/types-support": "10.10.1", - "@polkadot/util": "^12.5.1", - "@polkadot/util-crypto": "^12.5.1", - "@polkadot/x-fetch": "^12.5.1", - "@polkadot/x-global": "^12.5.1", - "@polkadot/x-ws": "^12.5.1", - "eventemitter3": "^5.0.1", - "mock-socket": "^9.3.1", - "nock": "^13.3.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@substrate/connect": "0.7.33" - } - }, - "node_modules/@polkadot/types": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-10.10.1.tgz", - "integrity": "sha512-Ben62P1tjYEhKag34GBGcLX6NqcFR1VD5nNbWaxgr+t36Jl/tlHs6P9DlbFqQP7Tt9FmGrAYY0m3oTkhjG1NzA==", - "dependencies": { - "@polkadot/keyring": "^12.5.1", - "@polkadot/types-augment": "10.10.1", - "@polkadot/types-codec": "10.10.1", - "@polkadot/types-create": "10.10.1", - "@polkadot/util": "^12.5.1", - "@polkadot/util-crypto": "^12.5.1", - "rxjs": "^7.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/types-augment": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-10.10.1.tgz", - "integrity": "sha512-XRHE75IocXfFE6EADYov3pqXCyBk5SWbiHoZ0+4WYWP9SwMuzsBaAy84NlhLBlkG3+ehIqi0HpAd/qrljJGZbg==", - "dependencies": { - "@polkadot/types": "10.10.1", - "@polkadot/types-codec": "10.10.1", - "@polkadot/util": "^12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/types-codec": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-10.10.1.tgz", - "integrity": "sha512-ETPG0wzWzt/bDKRQmYbO7CLe/0lUt8VrG6/bECdv+Kye+8Qedba2LZyTWm/9f2ngms8TZ82yI8mPv/mozdtfnw==", - "dependencies": { - "@polkadot/util": "^12.5.1", - "@polkadot/x-bigint": "^12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/types-create": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-10.10.1.tgz", - "integrity": "sha512-7OiLzd+Ter5zrpjP7fDwA1m89kd38VvMVixfOSv8x7ld2pDT+yyyKl14TCwRSWrKWCMtIb6M3iasPhq5cUa7cw==", - "dependencies": { - "@polkadot/types-codec": "10.10.1", - "@polkadot/util": "^12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/types-known": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-10.10.1.tgz", - "integrity": "sha512-yRa1lbDRqg3V/zoa0vSwdGOiYTIWktILW8OfkaLDExTu0GZBSbVHZlLAta52XVpA9Zww7mrUUC9+iernOwk//w==", - "dependencies": { - "@polkadot/networks": "^12.5.1", - "@polkadot/types": "10.10.1", - "@polkadot/types-codec": "10.10.1", - "@polkadot/types-create": "10.10.1", - "@polkadot/util": "^12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/types-support": { - "version": "10.10.1", - "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-10.10.1.tgz", - "integrity": "sha512-Cd2mwk9RG6LlX8X3H0bRY7wCTbZPqU3z38CMFhvNkFDAyjqKjtn8hpS4n8mMrZK2EwCs/MjQH1wb7rtFkaWmJw==", - "dependencies": { - "@polkadot/util": "^12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/util": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-12.5.1.tgz", - "integrity": "sha512-fDBZL7D4/baMG09Qowseo884m3QBzErGkRWNBId1UjWR99kyex+cIY9fOSzmuQxo6nLdJlLHw1Nz2caN3+Bq0A==", - "dependencies": { - "@polkadot/x-bigint": "12.5.1", - "@polkadot/x-global": "12.5.1", - "@polkadot/x-textdecoder": "12.5.1", - "@polkadot/x-textencoder": "12.5.1", - "@types/bn.js": "^5.1.1", - "bn.js": "^5.2.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/util-crypto": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-12.5.1.tgz", - "integrity": "sha512-Y8ORbMcsM/VOqSG3DgqutRGQ8XXK+X9M3C8oOEI2Tji65ZsXbh9Yh+ryPLM0oBp/9vqOXjkLgZJbbVuQceOw0A==", - "dependencies": { - "@noble/curves": "^1.2.0", - "@noble/hashes": "^1.3.2", - "@polkadot/networks": "12.5.1", - "@polkadot/util": "12.5.1", - "@polkadot/wasm-crypto": "^7.2.2", - "@polkadot/wasm-util": "^7.2.2", - "@polkadot/x-bigint": "12.5.1", - "@polkadot/x-randomvalues": "12.5.1", - "@scure/base": "^1.1.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "12.5.1" - } - }, - "node_modules/@polkadot/wasm-bridge": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.2.2.tgz", - "integrity": "sha512-CgNENd65DVYtackOVXXRA0D1RPoCv5+77IdBCf7kNqu6LeAnR4nfTI6qjaApUdN1xRweUsQjSH7tu7VjkMOA0A==", - "dependencies": { - "@polkadot/wasm-util": "7.2.2", - "tslib": "^2.6.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.2.2.tgz", - "integrity": "sha512-1ZY1rxUTawYm0m1zylvBMFovNIHYgG2v/XoASNp/EMG5c8FQIxCbhJRaTBA983GVq4lN/IAKREKEp9ZbLLqssA==", - "dependencies": { - "@polkadot/wasm-bridge": "7.2.2", - "@polkadot/wasm-crypto-asmjs": "7.2.2", - "@polkadot/wasm-crypto-init": "7.2.2", - "@polkadot/wasm-crypto-wasm": "7.2.2", - "@polkadot/wasm-util": "7.2.2", - "tslib": "^2.6.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-asmjs": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.2.2.tgz", - "integrity": "sha512-wKg+cpsWQCTSVhjlHuNeB/184rxKqY3vaklacbLOMbUXieIfuDBav5PJdzS3yeiVE60TpYaHW4iX/5OYHS82gg==", - "dependencies": { - "tslib": "^2.6.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-init": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.2.2.tgz", - "integrity": "sha512-vD4iPIp9x+SssUIWUenxWLPw4BVIwhXHNMpsV81egK990tvpyIxL205/EF5QRb1mKn8WfWcNFm5tYwwh9NdnnA==", - "dependencies": { - "@polkadot/wasm-bridge": "7.2.2", - "@polkadot/wasm-crypto-asmjs": "7.2.2", - "@polkadot/wasm-crypto-wasm": "7.2.2", - "@polkadot/wasm-util": "7.2.2", - "tslib": "^2.6.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" - } - }, - "node_modules/@polkadot/wasm-crypto-wasm": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.2.2.tgz", - "integrity": "sha512-3efoIB6jA3Hhv6k0YIBwCtlC8gCSWCk+R296yIXRLLr3cGN415KM/PO/d1JIXYI64lbrRzWRmZRhllw3jf6Atg==", - "dependencies": { - "@polkadot/wasm-util": "7.2.2", - "tslib": "^2.6.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/wasm-util": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.2.2.tgz", - "integrity": "sha512-N/25960ifCc56sBlJZ2h5UBpEPvxBmMLgwYsl7CUuT+ea2LuJW9Xh8VHDN/guYXwmm92/KvuendYkEUykpm/JQ==", - "dependencies": { - "tslib": "^2.6.1" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "*" - } - }, - "node_modules/@polkadot/x-bigint": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-12.5.1.tgz", - "integrity": "sha512-Fw39eoN9v0sqxSzfSC5awaDVdzojIiE7d1hRSQgVSrES+8whWvtbYMR0qwbVhTuW7DvogHmye41P9xKMlXZysg==", - "dependencies": { - "@polkadot/x-global": "12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/x-fetch": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-12.5.1.tgz", - "integrity": "sha512-Bc019lOKCoQJrthiS+H3LwCahGtl5tNnb2HK7xe3DBQIUx9r2HsF/uEngNfMRUFkUYg5TPCLFbEWU8NIREBS1A==", - "dependencies": { - "@polkadot/x-global": "12.5.1", - "node-fetch": "^3.3.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/x-global": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-12.5.1.tgz", - "integrity": "sha512-6K0YtWEg0eXInDOihU5aSzeb1t9TiDdX9ZuRly+58ALSqw5kPZYmQLbzE1d8HWzyXRXK+YH65GtLzfMGqfYHmw==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/x-randomvalues": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-12.5.1.tgz", - "integrity": "sha512-UsMb1d+77EPNjW78BpHjZLIm4TaIpfqq89OhZP/6gDIoS2V9iE/AK3jOWKm1G7Y2F8XIoX1qzQpuMakjfagFoQ==", - "dependencies": { - "@polkadot/x-global": "12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "12.5.1", - "@polkadot/wasm-util": "*" - } - }, - "node_modules/@polkadot/x-textdecoder": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-12.5.1.tgz", - "integrity": "sha512-j2YZGWfwhMC8nHW3BXq10fAPY02ObLL/qoTjCMJ1Cmc/OGq18Ep7k9cXXbjFAq3wf3tUUewt/u/hStKCk3IvfQ==", - "dependencies": { - "@polkadot/x-global": "12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/x-textencoder": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-12.5.1.tgz", - "integrity": "sha512-1JNNpOGb4wD+c7zFuOqjibl49LPnHNr4rj4s3WflLUIZvOMY6euoDuN3ISjQSHCLlVSoH0sOCWA3qXZU4bCTDQ==", - "dependencies": { - "@polkadot/x-global": "12.5.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/x-ws": { - "version": "12.5.1", - "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-12.5.1.tgz", - "integrity": "sha512-efNMhB3Lh6pW2iTipMkqwrjpuUtb3EwR/jYZftiIGo5tDPB7rqoMOp9s6KRFJEIUfZkLnMUtbkZ5fHzUJaCjmQ==", - "dependencies": { - "@polkadot/x-global": "12.5.1", - "tslib": "^2.6.2", - "ws": "^8.14.1" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@scure/base": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.3.tgz", - "integrity": "sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@substrate/connect": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.7.33.tgz", - "integrity": "sha512-1B984/bmXVQvTT9oV3c3b7215lvWmulP9rfP3T3Ri+OU3uIsyCzYw0A+XG6J8/jgO2FnroeNIBWlgoLaUM1uzw==", - "hasInstallScript": true, - "optional": true, - "dependencies": { - "@substrate/connect-extension-protocol": "^1.0.1", - "smoldot": "2.0.1" - } - }, - "node_modules/@substrate/connect-extension-protocol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz", - "integrity": "sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg==", - "optional": true - }, - "node_modules/@substrate/ss58-registry": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.44.0.tgz", - "integrity": "sha512-7lQ/7mMCzVNSEfDS4BCqnRnKCFKpcOaPrxMeGTXHX1YQzM/m2BBHjbK2C3dJvjv7GYxMiaTq/HdWQj1xS6ss+A==" - }, - "node_modules/@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" - }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "engines": { - "node": ">= 12" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "node_modules/mock-socket": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", - "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/nock": { - "version": "13.3.8", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.8.tgz", - "integrity": "sha512-96yVFal0c/W1lG7mmfRe7eO+hovrhJYd2obzzOZ90f6fjpeU/XNvd9cYHZKZAQJumDfhXgoTpkpJ9pvMj+hqHw==", - "dependencies": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 10.13" - } - }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, - "node_modules/propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "engines": { - "node": ">= 8" - } - }, - "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==", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/smoldot": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.1.tgz", - "integrity": "sha512-Wqw2fL/sELQByLSeeTX1Z/d0H4McmphPMx8vh6UZS/bIIDx81oU7s/drmx2iL/ME36uk++YxpRuJey8/MOyfOA==", - "optional": true, - "dependencies": { - "ws": "^8.8.1" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "engines": { - "node": ">= 8" - } - }, - "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "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 - } - } - } - } -} diff --git a/services/content-watcher/scripts/chain-setup/package.json b/services/content-watcher/scripts/chain-setup/package.json deleted file mode 100644 index 94821f7c..00000000 --- a/services/content-watcher/scripts/chain-setup/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "chain-setup", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "main": "node index.cjs", - "test": "echo \"Error: no test specified\" && exit 1" - }, - "type": "commonjs", - "author": "", - "license": "UNLICENSED", - "dependencies": { - "@polkadot/api": "^10.9.1", - "@polkadot/keyring": "^12.4.2", - "@polkadot/util": "^12.4.2", - "@frequency-chain/api-augment": "1.7.0" - - } -} diff --git a/services/content-watcher/scripts/local-setup.sh b/services/content-watcher/scripts/local-setup.sh index 60ec1431..3657c0cd 100755 --- a/services/content-watcher/scripts/local-setup.sh +++ b/services/content-watcher/scripts/local-setup.sh @@ -86,11 +86,15 @@ function teardown() { } function startup() { - # Start containers for chain & DB - docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} up -d + # Start containers for chain, ipfs & redis + ## start frequency service first as we want to set the chain state + docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} up -d frequency + + # Set up chain scenario, i.e. set provider, delegation and schemas + ( cd ${TOPDIR} && npm i && npm run local:init ) - # Set up chain scenario - ( cd ${TOPDIR}/scripts/chain-setup && npm i && npm run main) + # start rest of services + docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} up -d # publish some content ( cd ${TOPDIR}/scripts/content-setup && npm i && npm run main) From a6d27e08cba8393d741948a8ee847423ffc2b13d Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 16 Nov 2023 18:03:56 +0530 Subject: [PATCH 104/137] spin of all services and setup chain/content --- services/content-watcher/scripts/local-setup.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/content-watcher/scripts/local-setup.sh b/services/content-watcher/scripts/local-setup.sh index 3657c0cd..e0929b0b 100755 --- a/services/content-watcher/scripts/local-setup.sh +++ b/services/content-watcher/scripts/local-setup.sh @@ -96,6 +96,9 @@ function startup() { # start rest of services docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} up -d + # sleep for 5 seconds to wait for chain to start and service to be registered + sleep 5 + # publish some content ( cd ${TOPDIR}/scripts/content-setup && npm i && npm run main) From 541d364bb775f6bdae90b8e556298fbf32935eb7 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 16 Nov 2023 21:04:27 +0530 Subject: [PATCH 105/137] setup working e2e test setup --- services/content-watcher/.env.cp.docker.dev | 5 - .../{.env.docker.dev => .env.dev} | 8 +- services/content-watcher/Dockerfile | 4 +- services/content-watcher/Makefile | 4 + services/content-watcher/README.md | 29 ++++- .../apps/api/test/app.e2e-spec.ts | 114 +----------------- services/content-watcher/package.json | 3 +- services/content-watcher/scripts/local-cw.sh | 2 +- 8 files changed, 36 insertions(+), 133 deletions(-) rename services/content-watcher/{.env.docker.dev => .env.dev} (79%) diff --git a/services/content-watcher/.env.cp.docker.dev b/services/content-watcher/.env.cp.docker.dev index b108bf1f..97db7904 100644 --- a/services/content-watcher/.env.cp.docker.dev +++ b/services/content-watcher/.env.cp.docker.dev @@ -1,8 +1,3 @@ -# Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development -# IPFS_ENDPOINT="https://ipfs.infura.io:5001" -# IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" -# IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" -# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" IPFS_ENDPOINT="http://kubo_ipfs:5001" IPFS_BASIC_AUTH_USER="" IPFS_BASIC_AUTH_SECRET="" diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.dev similarity index 79% rename from services/content-watcher/.env.docker.dev rename to services/content-watcher/.env.dev index d5f44db9..aeb9795b 100644 --- a/services/content-watcher/.env.docker.dev +++ b/services/content-watcher/.env.dev @@ -3,14 +3,14 @@ # IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" # IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" # IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" -IPFS_ENDPOINT="http://kubo_ipfs:5001" +IPFS_ENDPOINT="http://127.0.0.1:5001" IPFS_BASIC_AUTH_USER="" IPFS_BASIC_AUTH_SECRET="" -IPFS_GATEWAY_URL="http://kubo_ipfs:8080/ipfs/[CID]" +IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" -FREQUENCY_URL=ws://frequency:9944 +FREQUENCY_URL=ws://127.0.0.1:9944 STARTING_BLOCK="0" -REDIS_URL=redis://redis:6379 +REDIS_URL=redis://127.0.0.1:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 HEALTH_CHECK_SUCCESS_THRESHOLD=10 diff --git a/services/content-watcher/Dockerfile b/services/content-watcher/Dockerfile index cdf91f45..131a8896 100644 --- a/services/content-watcher/Dockerfile +++ b/services/content-watcher/Dockerfile @@ -22,7 +22,5 @@ COPY package*.json ./ RUN npm install --only=production EXPOSE 3000 -ENV START_PROCESS="api" - -CMD ["sh", "-c", "if [ \"$START_PROCESS\" = \"api\" ]; then npm run start:api:prod; else npm run start:worker:prod; fi"] +CMD ["sh", "-c", "npm run start:api:prod"] diff --git a/services/content-watcher/Makefile b/services/content-watcher/Makefile index a23d953e..0d00f970 100644 --- a/services/content-watcher/Makefile +++ b/services/content-watcher/Makefile @@ -36,6 +36,10 @@ test-services-start: test-services-stop: @scripts/local-setup.sh -n cw-e2e -d +.PHONY: test-e2e +test-e2e: + @(npm run test:e2e) + ###### ###### Misc targets ###### diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index dbdfb83d..a0380ca3 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -9,6 +9,7 @@ Content Watcher is a service that watches for events on Frequency and produces D - [Prerequisites](#prerequisites) - [Getting Started](#getting-started) - [Clone the Repository](#clone-the-repository) + - [Run a full e2e test](#run-a-full-e2e-test) ## Prerequisites @@ -28,14 +29,30 @@ Follow these steps to set up and run Content Watcher: git clone https://github.com/amplicalabls/content-watcher-service.git ``` -2. Modify any environment variables in the `.env` file as needed. For docker compose env `.env.docker.dev` file is used. +### Run a full e2e test -3. Run the following command to start the service: +1. Run the following make command to spin up the entire stack: ```bash - docker-compose -f docker-compose.dev.yaml up - ``` + make test-start-services + ``` + + This will setup the following services: + + - **Frequency:** A local instance of Frequency will be with default as instant sealing mode. + - **Redis:** A local instance of Redis will be spun up and configured to be used by content publishing and content watcher services. + - **Kubo IPFS:** A local instance of IPFS will be spun up and configured to be used for content publishing and retrieval. + - **Content Publishing API**: A local instance of the content publishing API will be used to publish content to IPFS and Frequency for content watcher tests. + - **Content Publishing Worker**: A local instance of the content publishing worker will be used to publish content to IPFS and Frequency for content watcher tests via dedicated processors. -4. Visit [Swagger UI](http://localhost:3000/api/docs/swagger) to view the API documentation and submit requests to the service. + Following setup scenarios will be run while stack is brought up: -5. Visit [Bullboard](http://localhost:3000/queues) to view the job queue and the status of the jobs. + - **Chain Setup Scenario**: A provider with MSA=1 will be created with some users accounts along with delegation to provider. Capacity will be staked to MSA=1 to enable provider to publish content on behalf of users. + - **DSNP Schemas**: DSNP schemas will be registered on Frequency. + - **Publish some example content**: Some example content will be published to IPFS and Frequency. Check out [Content Publishing BullBoard](http://0.0.0.0:3001/queues) to see the progress of content publishing. + +2. Run the following make command to run the content watcher tests: + + ```bash + make test-e2e + ``` diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index 6caa8f36..0f60bab0 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -6,56 +6,11 @@ import request from 'supertest'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { ApiModule } from '../src/api.module'; -describe('Content Publishing E2E request verification!', () => { +describe('Content Watcher E2E request verification!', () => { let app: INestApplication; let module: TestingModule; // eslint-disable-next-line no-promise-executor-return const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - const validLocation = { - name: 'name of location', - accuracy: 97, - altitude: 10, - latitude: 37.26, - longitude: -119.59, - radius: 10, - units: 'm', - }; - const validTags = [ - { - type: 'mention', - mentionedId: 'dsnp://78187493520', - }, - { - type: 'hashtag', - name: '#taggedUser', - }, - ]; - const validContentNoUploadedAssets = { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - name: 'name of note content', - assets: [ - { - type: 'link', - name: 'link asset', - href: 'http://example.com', - }, - ], - tag: validTags, - location: validLocation, - }; - const validBroadCastNoUploadedAssets = { - content: validContentNoUploadedAssets, - }; - const validReplyNoUploadedAssets = { - content: validContentNoUploadedAssets, - inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - }; - const validReaction = { - emoji: '🤌🏼', - apply: 5, - inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - }; beforeEach(async () => { module = await Test.createTestingModule({ @@ -74,73 +29,6 @@ describe('Content Publishing E2E request verification!', () => { it('(GET) /api/health', () => request(app.getHttpServer()).get('/api/health').expect(200).expect({ status: 200 })); - describe('Validate Route params', () => { - it('invalid userDsnpId should fail', async () => { - const invalidDsnpUserId = '2gsjhdaj'; - return request(app.getHttpServer()) - .post(`/api/content/${invalidDsnpUserId}/broadcast`) - .send(validBroadCastNoUploadedAssets) - .expect(400) - .expect((res) => expect(res.text).toContain('must be a number string')); - }); - }); - - describe('(POST) /api/content/:dsnpUserId/broadcast', () => { - it('valid request without uploaded assets should work!', () => - request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(validBroadCastNoUploadedAssets) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - - it('valid broadcast request with uploaded assets should work!', async () => { - const file = Buffer.from('g'.repeat(30 * 1000 * 1000)); // 30MB - const response = await request(app.getHttpServer()).put(`/api/asset/upload`).attach('files', file, 'file1.jpg').expect(202); - await sleep(1000); - const validBroadCastWithUploadedAssets = { - content: { - ...validContentNoUploadedAssets, - assets: [ - { - type: 'image', - name: 'image asset', - references: [ - { - referenceId: response.body.assetIds[0], - height: 123, - width: 321, - }, - ], - }, - ], - }, - }; - return request(app.getHttpServer()) - .post(`/api/content/123/broadcast`) - .send(validBroadCastWithUploadedAssets) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId')); - }, 15000); - }); - - describe('(POST) /api/content/:dsnpUserId/reply', () => { - it('valid request without assets should work!', () => - request(app.getHttpServer()) - .post(`/api/content/123/reply`) - .send(validReplyNoUploadedAssets) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - }); - - describe('(POST) /api/content/:dsnpUserId/reaction', () => { - it('valid request should work!', () => - request(app.getHttpServer()) - .post(`/api/content/123/reaction`) - .send(validReaction) - .expect(202) - .expect((res) => expect(res.text).toContain('referenceId'))); - }); - afterEach(async () => { try { await app.close(); diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index ecca90ff..a6158026 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -9,6 +9,7 @@ "generate-swagger-ui": "redoc-cli bundle swagger.yaml --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "start:api": "nest start api", + "start:api:prod": "node dist/apps/api/main.js", "start:api:dev": "set -a ; . .env ; nest start api", "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", "docker-build": "docker build -t content-watcher-service .", @@ -20,7 +21,7 @@ "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "pretest": "cp env.template .env", "test": "jest --coverage --verbose", - "test:e2e": "set -a ; . .env ; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", + "test:e2e": "set -a ; . ./.env.dev; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", "local:init": "node scripts/chain-setup/local-chain-setup.cjs", "local:publish": "cd scripts/content-setup && npm i && npm run main" }, diff --git a/services/content-watcher/scripts/local-cw.sh b/services/content-watcher/scripts/local-cw.sh index c49d8844..bcc3c318 100755 --- a/services/content-watcher/scripts/local-cw.sh +++ b/services/content-watcher/scripts/local-cw.sh @@ -26,7 +26,7 @@ npm run build # Make sure the correct set of services is running set -a -. .env.docker.dev +. .env.dev pm2 delete all pm2 start --cwd ${TOPDIR} ${TOPDIR}/tools/scripts/test-pm2.config.js --only mock-service From a8d56e458e44c119ebb6562acf0ccd0ba13c2c2d Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 16 Nov 2023 22:40:42 +0530 Subject: [PATCH 106/137] add some basic api tests --- .../apps/api/test/app.e2e-spec.ts | 23 +++++++++++++++++++ .../src/blockchain/blockchain.service.ts | 5 ++++ 2 files changed, 28 insertions(+) diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index 0f60bab0..1742e52b 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -5,6 +5,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import request from 'supertest'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { ApiModule } from '../src/api.module'; +import { BlockchainService } from '../../../libs/common/src/blockchain/blockchain.service'; describe('Content Watcher E2E request verification!', () => { let app: INestApplication; @@ -25,10 +26,32 @@ describe('Content Watcher E2E request verification!', () => { app.useGlobalPipes(new ValidationPipe()); app.enableShutdownHooks(); await app.init(); + const blockchainService = app.get(BlockchainService); + await blockchainService.isReady(); + }); + it('(Put) /api/registerWebhook', async () => { + const webhookRegistrationDto = { + url: 'http://localhost:3000/api/webhook', + announcementTypes: ['Broadcast', 'Reaction', 'Tombstone', 'Reply', 'Update'], + }; + const response = await request(app.getHttpServer()) + .put('/api/registerWebhook') + .send(webhookRegistrationDto) + .expect(200); }); it('(GET) /api/health', () => request(app.getHttpServer()).get('/api/health').expect(200).expect({ status: 200 })); + it('(Post) /api/resetScanner', async () => { + const resetScannerDto = { + blockNumber: 0, + }; + const response = await request(app.getHttpServer()) + .post('/api/resetScanner') + .send(resetScannerDto) + .expect(201); + }); + afterEach(async () => { try { await app.close(); diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 3b99f815..67a558d0 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -39,6 +39,11 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS this.logger.log('Blockchain API ready.'); } + public async isReady(): Promise { + await this.apiPromise.isReady; + return true; + } + public async onApplicationShutdown(signal?: string | undefined) { const promises: Promise[] = []; if (this.api) { From a7505824015b5530bac74b193e7a41ea491d232f Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Thu, 16 Nov 2023 22:45:17 +0530 Subject: [PATCH 107/137] add more e2e tests --- .../apps/api/test/app.e2e-spec.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index 1742e52b..c70f788d 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -28,8 +28,8 @@ describe('Content Watcher E2E request verification!', () => { await app.init(); const blockchainService = app.get(BlockchainService); await blockchainService.isReady(); - }); - it('(Put) /api/registerWebhook', async () => { + + // register webhook '(Put) /api/registerWebhook' const webhookRegistrationDto = { url: 'http://localhost:3000/api/webhook', announcementTypes: ['Broadcast', 'Reaction', 'Tombstone', 'Reply', 'Update'], @@ -52,6 +52,20 @@ describe('Content Watcher E2E request verification!', () => { .expect(201); }); + it('(Put) /api/search - search for content', async () => { + const searchRequest = { + startBlock: '0', + endBlock: '100', + }; + const response = await request(app.getHttpServer()) + .put('/api/search') + .send(searchRequest) + .expect(200); + expect(response.body).toHaveProperty('jobId'); + const jobId = response.body.jobId; + expect(jobId).not.toBeNull(); + }); + afterEach(async () => { try { await app.close(); From 3a996cb43b9bc7cc6088334bf7f9197f82f1eb7c Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Fri, 17 Nov 2023 10:22:29 +0530 Subject: [PATCH 108/137] cleanips --- .../apps/api/test/app.e2e-spec.ts | 30 ++++++------ .../src/blockchain/blockchain.service.ts | 2 +- .../libs/common/src/crawler/crawler.ts | 48 ++++++++++++------- .../libs/common/src/dtos/common.dto.ts | 4 +- .../src/interfaces/announcement_response.ts | 9 +++- .../src/interfaces/ipfs.job.interface.ts | 7 +-- .../libs/common/src/scanner/scanner.ts | 48 ++++++++++++------- services/content-watcher/package-lock.json | 7 +-- services/content-watcher/package.json | 1 + 9 files changed, 94 insertions(+), 62 deletions(-) diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index c70f788d..ca1e4f77 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -4,8 +4,10 @@ import { INestApplication, ValidationPipe } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import request from 'supertest'; import { EventEmitter2 } from '@nestjs/event-emitter'; +import nock from 'nock'; import { ApiModule } from '../src/api.module'; import { BlockchainService } from '../../../libs/common/src/blockchain/blockchain.service'; +import { ResetScannerDto } from '../../../libs/common/src'; describe('Content Watcher E2E request verification!', () => { let app: INestApplication; @@ -29,27 +31,26 @@ describe('Content Watcher E2E request verification!', () => { const blockchainService = app.get(BlockchainService); await blockchainService.isReady(); + nock('http://localhost:3005').post('/api/webhook').reply(200, { success: true }); + // register webhook '(Put) /api/registerWebhook' const webhookRegistrationDto = { - url: 'http://localhost:3000/api/webhook', + url: 'http://localhost:3005/api/webhook', announcementTypes: ['Broadcast', 'Reaction', 'Tombstone', 'Reply', 'Update'], }; - const response = await request(app.getHttpServer()) - .put('/api/registerWebhook') - .send(webhookRegistrationDto) - .expect(200); + const response = await request(app.getHttpServer()).put('/api/registerWebhook').send(webhookRegistrationDto).expect(200); }); it('(GET) /api/health', () => request(app.getHttpServer()).get('/api/health').expect(200).expect({ status: 200 })); it('(Post) /api/resetScanner', async () => { - const resetScannerDto = { - blockNumber: 0, + const resetScannerDto: ResetScannerDto = { + blockNumber: '0', }; - const response = await request(app.getHttpServer()) - .post('/api/resetScanner') - .send(resetScannerDto) - .expect(201); + const response = await request(app.getHttpServer()).post('/api/resetScanner').send(resetScannerDto).expect(201); + + // wait for the scanner to finish + await sleep(5000); }); it('(Put) /api/search - search for content', async () => { @@ -57,12 +58,9 @@ describe('Content Watcher E2E request verification!', () => { startBlock: '0', endBlock: '100', }; - const response = await request(app.getHttpServer()) - .put('/api/search') - .send(searchRequest) - .expect(200); + const response = await request(app.getHttpServer()).put('/api/search').send(searchRequest).expect(200); expect(response.body).toHaveProperty('jobId'); - const jobId = response.body.jobId; + const { jobId } = response.body; expect(jobId).not.toBeNull(); }); diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 67a558d0..43f30308 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -43,7 +43,7 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS await this.apiPromise.isReady; return true; } - + public async onApplicationShutdown(signal?: string | undefined) { const promises: Promise[] = []; if (this.api) { diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 35b4d470..24becab4 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -16,6 +16,7 @@ import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { ContentSearchRequestDto } from '../dtos/request-job.dto'; import { REGISTERED_WEBHOOK_KEY } from '../constants'; +import { MessageResponseWithSchemaId } from '../interfaces/announcement_response'; @Injectable() @Processor(QueueConstants.REQUEST_QUEUE_NAME, { @@ -78,8 +79,8 @@ export class CrawlerService extends BaseConsumer { return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); } - private async processEvents(events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { - const filteredEvents: (Vec | null)[] = await Promise.all( + private async processEvents(events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { + const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( events.map(async (event) => { if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { @@ -109,36 +110,47 @@ export class CrawlerService extends BaseConsumer { messages.push(...messageResponse.content); } } - return messages; + const messagesWithSchemaId: MessageResponseWithSchemaId = { + schemaId: schemaId.toString(), + messages, + }; + return messagesWithSchemaId; } return null; }), ); - - const collectedMessages: MessageResponse[] = []; + const collectedMessages: MessageResponseWithSchemaId[] = []; filteredEvents.forEach((event) => { if (event) { - collectedMessages.push(...event.toArray()); + collectedMessages.push(event); } }); + return collectedMessages; } - private async queueIPFSJobs(id: string, messages: MessageResponse[]) { + private async queueIPFSJobs(requestId: string, messages: MessageResponseWithSchemaId[]): Promise { const promises = messages.map(async (messageResponse) => { - if (!messageResponse.cid || messageResponse.cid.isNone) { - return; - } + const { schemaId } = messageResponse; + const innerPromises = messageResponse.messages.map(async (message) => { + if (!message.cid || message.cid.isNone) { + return; + } + + const ipfsQueueJob = createIPFSQueueJob( + message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), + message.provider_msa_id.toString(), + schemaId, + message.cid.unwrap().toString(), + message.index.toNumber(), + requestId, + ); + // eslint-disable-next-line no-await-in-loop + await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); + }); - const ipfsQueueJob = createIPFSQueueJob( - messageResponse.msa_id.isNone ? messageResponse.provider_msa_id.toString() : messageResponse.msa_id.unwrap().toString(), - messageResponse.provider_msa_id.toString(), - messageResponse.cid.unwrap().toString(), - messageResponse.index.toNumber(), - id, - ); // eslint-disable-next-line no-await-in-loop - await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); + await Promise.all(innerPromises); }); await Promise.all(promises); diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index 07e45cd2..74a1c391 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -25,8 +25,8 @@ export class FilesUploadDto { } export class ResetScannerDto { - @ApiProperty() - blockNumber?: bigint; + @ApiProperty({ type: 'string', description: 'The block number to reset the scanner to', example: '0' }) + blockNumber: string; } // eslint-disable-next-line no-shadow diff --git a/services/content-watcher/libs/common/src/interfaces/announcement_response.ts b/services/content-watcher/libs/common/src/interfaces/announcement_response.ts index 7888fa0d..43f9376b 100644 --- a/services/content-watcher/libs/common/src/interfaces/announcement_response.ts +++ b/services/content-watcher/libs/common/src/interfaces/announcement_response.ts @@ -1,7 +1,14 @@ +import { MessageResponse } from '@frequency-chain/api-augment/interfaces'; +import { Vec } from '@polkadot/types'; import { Announcement } from './dsnp'; export interface AnnouncementResponse { requestId?: string; - schemaId: number; + schemaId: string; announcement: Announcement; } + +export interface MessageResponseWithSchemaId { + schemaId: string; + messages: Vec; +} diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index 4d276054..5ac2d480 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -1,19 +1,20 @@ export interface IIPFSJob { msaId: string; providerId: string; + schemaId: string; cid: string; - schemaId: number; - blockNumber: bigint; + blockNumber: string; index: number; requestId?: string; } -export function createIPFSQueueJob(msaId: string, providerId: string, cid: string, index: number, requestId: string): { key: string; data: IIPFSJob } { +export function createIPFSQueueJob(msaId: string, providerId: string, schemaId: string, cid: string, index: number, requestId: string): { key: string; data: IIPFSJob } { return { key: `${msaId}:${providerId}:${index}:${requestId}`, data: { msaId, providerId, + schemaId, cid, index, requestId, diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 05ac0c54..110b8eb5 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -17,6 +17,7 @@ import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEB import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import { RedisUtils } from '../utils/redis'; +import { MessageResponseWithSchemaId } from '../interfaces/announcement_response'; @Injectable() export class ScannerService implements OnApplicationBootstrap { @@ -137,8 +138,8 @@ export class ScannerService implements OnApplicationBootstrap { } } - private async processEvents(events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { - const filteredEvents: (Vec | null)[] = await Promise.all( + private async processEvents(events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { + const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( events.map(async (event) => { if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { @@ -168,36 +169,47 @@ export class ScannerService implements OnApplicationBootstrap { messages.push(...messageResponse.content); } } - return messages; + const messagesWithSchemaId: MessageResponseWithSchemaId = { + schemaId: schemaId.toString(), + messages, + }; + return messagesWithSchemaId; } return null; }), ); - - const collectedMessages: MessageResponse[] = []; + const collectedMessages: MessageResponseWithSchemaId[] = []; filteredEvents.forEach((event) => { if (event) { - collectedMessages.push(...event.toArray()); + collectedMessages.push(event); } }); + return collectedMessages; } - private async queueIPFSJobs(messages: MessageResponse[]) { + private async queueIPFSJobs(messages: MessageResponseWithSchemaId[]): Promise { const promises = messages.map(async (messageResponse) => { - if (!messageResponse.cid || messageResponse.cid.isNone) { - return; - } + const { schemaId } = messageResponse; + const innerPromises = messageResponse.messages.map(async (message) => { + if (!message.cid || message.cid.isNone) { + return; + } + + const ipfsQueueJob = createIPFSQueueJob( + message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), + message.provider_msa_id.toString(), + schemaId, + message.cid.unwrap().toString(), + message.index.toNumber(), + '', + ); + // eslint-disable-next-line no-await-in-loop + await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); + }); - const ipfsQueueJob = createIPFSQueueJob( - messageResponse.msa_id.isNone ? messageResponse.provider_msa_id.toString() : messageResponse.msa_id.unwrap().toString(), - messageResponse.provider_msa_id.toString(), - messageResponse.cid.unwrap().toString(), - messageResponse.index.toNumber(), - '', - ); // eslint-disable-next-line no-await-in-loop - await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); + await Promise.all(innerPromises); }); await Promise.all(promises); diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 37f06a25..0ac921c6 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -76,6 +76,7 @@ "ioredis-mock": "^8.8.3", "jest": "^29.5.0", "license-report": "^6.4.0", + "nock": "^13.3.8", "prettier": "^3.0.2", "redoc-cli": "^0.13.21", "source-map-support": "^0.5.21", @@ -9629,12 +9630,12 @@ } }, "node_modules/nock": { - "version": "13.3.1", - "license": "MIT", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.8.tgz", + "integrity": "sha512-96yVFal0c/W1lG7mmfRe7eO+hovrhJYd2obzzOZ90f6fjpeU/XNvd9cYHZKZAQJumDfhXgoTpkpJ9pvMj+hqHw==", "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.21", "propagate": "^2.0.0" }, "engines": { diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index a6158026..8f505106 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -104,6 +104,7 @@ "ioredis-mock": "^8.8.3", "jest": "^29.5.0", "license-report": "^6.4.0", + "nock": "^13.3.8", "prettier": "^3.0.2", "redoc-cli": "^0.13.21", "source-map-support": "^0.5.21", From 002223b2b3f09d12bb7f605f564093ed2a216f38 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Fri, 17 Nov 2023 11:02:57 +0530 Subject: [PATCH 109/137] also send blocknumber where annoucement is found --- .../libs/common/src/crawler/crawler.ts | 1 + .../common/src/interfaces/announcement_response.ts | 1 + .../libs/common/src/interfaces/ipfs.job.interface.ts | 11 ++++++++++- .../content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts | 7 +++++++ .../libs/common/src/scanner/scanner.ts | 1 + 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 24becab4..353feac4 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -138,6 +138,7 @@ export class CrawlerService extends BaseConsumer { } const ipfsQueueJob = createIPFSQueueJob( + message.block_number.toString(), message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), message.provider_msa_id.toString(), schemaId, diff --git a/services/content-watcher/libs/common/src/interfaces/announcement_response.ts b/services/content-watcher/libs/common/src/interfaces/announcement_response.ts index 43f9376b..7928700e 100644 --- a/services/content-watcher/libs/common/src/interfaces/announcement_response.ts +++ b/services/content-watcher/libs/common/src/interfaces/announcement_response.ts @@ -5,6 +5,7 @@ import { Announcement } from './dsnp'; export interface AnnouncementResponse { requestId?: string; schemaId: string; + blockNumber: string; announcement: Announcement; } diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index 5ac2d480..c11bac11 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -8,7 +8,15 @@ export interface IIPFSJob { requestId?: string; } -export function createIPFSQueueJob(msaId: string, providerId: string, schemaId: string, cid: string, index: number, requestId: string): { key: string; data: IIPFSJob } { +export function createIPFSQueueJob( + blockNumber: string, + msaId: string, + providerId: string, + schemaId: string, + cid: string, + index: number, + requestId: string, +): { key: string; data: IIPFSJob } { return { key: `${msaId}:${providerId}:${index}:${requestId}`, data: { @@ -16,6 +24,7 @@ export function createIPFSQueueJob(msaId: string, providerId: string, schemaId: providerId, schemaId, cid, + blockNumber, index, requestId, } as IIPFSJob, diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index dffc5e08..d378b73d 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -76,12 +76,14 @@ export class IPFSContentProcessor extends BaseConsumer { private async buildAndQueueDSNPAnnouncements(records: any[], jobData: IIPFSJob): Promise { const jobRequestId = jobData.requestId; + const blockNumer = jobData.blockNumber; records.forEach(async (mapRecord) => { switch (mapRecord.announcementType) { case AnnouncementType.Broadcast: { const recordAnnouncement = mapRecord as BroadcastAnnouncement; const broadCastResponse: AnnouncementResponse = { schemaId: jobData.schemaId, + blockNumber: blockNumer, announcement: { fromId: recordAnnouncement.fromId, contentHash: bases.base58btc.encode(recordAnnouncement.contentHash as any), @@ -100,6 +102,7 @@ export class IPFSContentProcessor extends BaseConsumer { const tombRecord = mapRecord as TombstoneAnnouncement; const tombstoneResponse: AnnouncementResponse = { schemaId: jobData.schemaId, + blockNumber: blockNumer, announcement: { fromId: tombRecord.fromId, targetAnnouncementType: tombRecord.targetAnnouncementType, @@ -118,6 +121,7 @@ export class IPFSContentProcessor extends BaseConsumer { const reactionRecord = mapRecord as ReactionAnnouncement; const reactionResponse: AnnouncementResponse = { schemaId: jobData.schemaId, + blockNumber: blockNumer, announcement: { fromId: reactionRecord.fromId, announcementType: reactionRecord.announcementType, @@ -137,6 +141,7 @@ export class IPFSContentProcessor extends BaseConsumer { const replyRecord = mapRecord as ReplyAnnouncement; const replyResponse: AnnouncementResponse = { schemaId: jobData.schemaId, + blockNumber: blockNumer, announcement: { fromId: replyRecord.fromId, announcementType: replyRecord.announcementType, @@ -156,6 +161,7 @@ export class IPFSContentProcessor extends BaseConsumer { const profileRecord = mapRecord as ProfileAnnouncement; const profileResponse: AnnouncementResponse = { schemaId: jobData.schemaId, + blockNumber: blockNumer, announcement: { fromId: profileRecord.fromId, announcementType: profileRecord.announcementType, @@ -174,6 +180,7 @@ export class IPFSContentProcessor extends BaseConsumer { const updateRecord = mapRecord as UpdateAnnouncement; const updateResponse: AnnouncementResponse = { schemaId: jobData.schemaId, + blockNumber: blockNumer, announcement: { fromId: updateRecord.fromId, announcementType: updateRecord.announcementType, diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 110b8eb5..181e86ca 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -197,6 +197,7 @@ export class ScannerService implements OnApplicationBootstrap { } const ipfsQueueJob = createIPFSQueueJob( + message.block_number.toString(), message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), message.provider_msa_id.toString(), schemaId, From 695c8b117eede6587347e48c75cd16bc5aefca1e Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Fri, 17 Nov 2023 14:14:39 +0530 Subject: [PATCH 110/137] add simple e2e tests --- services/content-watcher/apps/api/test/app.e2e-spec.ts | 7 ++----- .../content-watcher/libs/common/src/crawler/crawler.ts | 2 +- .../content-watcher/libs/common/src/scanner/scanner.ts | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index ca1e4f77..5dafd571 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -48,10 +48,7 @@ describe('Content Watcher E2E request verification!', () => { blockNumber: '0', }; const response = await request(app.getHttpServer()).post('/api/resetScanner').send(resetScannerDto).expect(201); - - // wait for the scanner to finish - await sleep(5000); - }); + }, 15000); it('(Put) /api/search - search for content', async () => { const searchRequest = { @@ -62,7 +59,7 @@ describe('Content Watcher E2E request verification!', () => { expect(response.body).toHaveProperty('jobId'); const { jobId } = response.body; expect(jobId).not.toBeNull(); - }); + }, 15000); afterEach(async () => { try { diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 353feac4..3b16810b 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -83,7 +83,7 @@ export class CrawlerService extends BaseConsumer { const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( events.map(async (event) => { if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { - if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { + if (eventsToWatch.schemaIds && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { return null; } const schemaId = event.event.data[0] as SchemaId; diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 181e86ca..5a8d201a 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -142,7 +142,7 @@ export class ScannerService implements OnApplicationBootstrap { const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( events.map(async (event) => { if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { - if (eventsToWatch.schemaIds.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { + if (eventsToWatch.schemaIds && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { return null; } const schemaId = event.event.data[0] as SchemaId; From b11ae6d6ea691e71e4eb5709aca7575e73a0ee68 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Fri, 17 Nov 2023 18:37:19 +0530 Subject: [PATCH 111/137] set simple e2e test --- services/content-watcher/README.md | 2 ++ services/content-watcher/apps/api/test/app.e2e-spec.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index a0380ca3..977404ba 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -56,3 +56,5 @@ Follow these steps to set up and run Content Watcher: ```bash make test-e2e ``` + +3. Alternatively, create .env file, run `nest start api` to content watcher as standalone, register a webhook with content watcher using [swagger](http://0.0.0.0:3000/api/docs/swagger#) and try some api to scan content. diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index 5dafd571..042c8587 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -31,7 +31,6 @@ describe('Content Watcher E2E request verification!', () => { const blockchainService = app.get(BlockchainService); await blockchainService.isReady(); - nock('http://localhost:3005').post('/api/webhook').reply(200, { success: true }); // register webhook '(Put) /api/registerWebhook' const webhookRegistrationDto = { From 4cc9832a14bc554761f229e0dd78b6452820e157 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Fri, 17 Nov 2023 20:22:28 +0530 Subject: [PATCH 112/137] also allow to pull specific dsnpIds --- services/content-watcher/README.md | 5 ++- .../apps/api/src/api.service.ts | 9 +++-- .../apps/api/test/app.e2e-spec.ts | 1 - .../libs/common/src/crawler/crawler.ts | 2 +- .../libs/common/src/pubsub/pubsub.service.ts | 36 +++++++++++++++++-- .../libs/common/src/scanner/scanner.ts | 3 +- 6 files changed, 46 insertions(+), 10 deletions(-) diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index 977404ba..1b8b7a1b 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -57,4 +57,7 @@ Follow these steps to set up and run Content Watcher: make test-e2e ``` -3. Alternatively, create .env file, run `nest start api` to content watcher as standalone, register a webhook with content watcher using [swagger](http://0.0.0.0:3000/api/docs/swagger#) and try some api to scan content. +3. Alternatively, create .env file, run `nest start api` to start content watcher as standalone, register a webhook with content watcher using [swagger](http://0.0.0.0:3000/api/docs/swagger#) and try following scenarios: + + - **Reset scanner**: Reset scanner will reset the scanner to start from the beginning of the chain or whichever block is chose to start with. Upon successful parse respective announcement will be made to the webhook. + - **Put a search request**: Put a search request will put a search request on the queue. The request requires a start block and end block. Upon successful parse respective announcement will be made to the webhook. diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index c3565a50..d923ae33 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -32,7 +32,7 @@ export class ApiService { this.logger.warn(`Setting watch options to ${JSON.stringify(watchOptions)}`); const currentWatchOptions = await this.redis.get(EVENTS_TO_WATCH_KEY); this.logger.warn(`Current watch options are ${currentWatchOptions}`); - await this.redis.set(EVENTS_TO_WATCH_KEY, JSON.stringify(watchOptions)); + await this.redis.setex(EVENTS_TO_WATCH_KEY, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, JSON.stringify(watchOptions)); } public pauseScanner() { @@ -55,8 +55,11 @@ export class ApiService { return job; } this.requestQueue.remove(jobId); - const jobPromise = this.requestQueue.add(`Content Search ${jobId}`, contentSearchRequestDto, { jobId }); - this.logger.debug(`Added job ${jobId}`); + // eslint-disable-next-line no-param-reassign + contentSearchRequestDto.id = jobId; + const jobPromise = await this.requestQueue.add(`Content Search ${jobId}`, contentSearchRequestDto, { jobId }); + const JOB_REQUEST_WATCH_KEY = `${EVENTS_TO_WATCH_KEY}:${jobId}`; + await this.redis.setex(JOB_REQUEST_WATCH_KEY, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, JSON.stringify(contentSearchRequestDto.filters)); return jobPromise; } diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index 042c8587..d52ac212 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -31,7 +31,6 @@ describe('Content Watcher E2E request verification!', () => { const blockchainService = app.get(BlockchainService); await blockchainService.isReady(); - // register webhook '(Put) /api/registerWebhook' const webhookRegistrationDto = { url: 'http://localhost:3005/api/webhook', diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 3b16810b..0c51daf5 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -83,7 +83,7 @@ export class CrawlerService extends BaseConsumer { const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( events.map(async (event) => { if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { - if (eventsToWatch.schemaIds && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { + if (eventsToWatch?.schemaIds?.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0]?.toString())) { return null; } const schemaId = event.event.data[0] as SchemaId; diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts index b42f1f03..1e451d5e 100644 --- a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts @@ -2,9 +2,10 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Injectable, Logger } from '@nestjs/common'; import Redis from 'ioredis'; import axios from 'axios'; -import { REGISTERED_WEBHOOK_KEY } from '../constants'; +import { EVENTS_TO_WATCH_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; import { AnnouncementResponse } from '../interfaces/announcement_response'; import { ConfigService } from '../config/config.service'; +import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; @Injectable() export class PubSubService { @@ -19,7 +20,13 @@ export class PubSubService { async process(message: AnnouncementResponse, messageType: string) { this.logger.debug(`Sending announcements to webhooks`); - + // if any specific dsnpIds are request out of system, filter others out + if (message.requestId && !(await this.filterJobRequest(message.requestId, message.announcement.fromId))) { + return; + } + if (!(await this.filterChainWatch(message.announcement.fromId))) { + return; + } // Get the registered webhooks for the specific messageType const registeredWebhook = await this.redis.get(REGISTERED_WEBHOOK_KEY); let currentWebhookRegistrationDtos: { announcementType: string; urls: string[] }[] = []; @@ -53,4 +60,29 @@ export class PubSubService { }); } } + + private async filterChainWatch(dsnpId: string): Promise { + const currentWatchOptions = await this.redis.get(EVENTS_TO_WATCH_KEY); + const watchOptions: ChainWatchOptionsDto = currentWatchOptions ? JSON.parse(currentWatchOptions) : null; + if (watchOptions?.dsnpIds?.length > 0) { + if (watchOptions.dsnpIds.includes(dsnpId)) { + return true; + } + return false; + } + return true; + } + + private async filterJobRequest(jobId: string, dsnpId: string): Promise { + const JOB_REQUEST_WATCH_KEY = `${EVENTS_TO_WATCH_KEY}:${jobId}`; + const jobRequestWatch = await this.redis.get(JOB_REQUEST_WATCH_KEY); + const jobRequestWatchOptions: ChainWatchOptionsDto = jobRequestWatch ? JSON.parse(jobRequestWatch) : null; + if (jobRequestWatchOptions?.dsnpIds?.length > 0) { + if (jobRequestWatchOptions.dsnpIds.includes(dsnpId)) { + return true; + } + return false; + } + return true; + } } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 5a8d201a..787b350a 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -142,7 +142,7 @@ export class ScannerService implements OnApplicationBootstrap { const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( events.map(async (event) => { if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { - if (eventsToWatch.schemaIds && !eventsToWatch.schemaIds.includes(event.event.data[0].toString())) { + if (eventsToWatch?.schemaIds?.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0]?.toString())) { return null; } const schemaId = event.event.data[0] as SchemaId; @@ -184,7 +184,6 @@ export class ScannerService implements OnApplicationBootstrap { collectedMessages.push(event); } }); - return collectedMessages; } From 373e9944585e1ac4d1201b9e89ddf59a868d7dc2 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat Date: Sat, 18 Nov 2023 08:04:34 +0530 Subject: [PATCH 113/137] cleanup readme --- services/content-watcher/README.md | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index 1b8b7a1b..3b984da1 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -9,7 +9,7 @@ Content Watcher is a service that watches for events on Frequency and produces D - [Prerequisites](#prerequisites) - [Getting Started](#getting-started) - [Clone the Repository](#clone-the-repository) - - [Run a full e2e test](#run-a-full-e2e-test) + - [Run a Full End-to-End Test](#run-a-full-end-to-end-test) ## Prerequisites @@ -29,35 +29,35 @@ Follow these steps to set up and run Content Watcher: git clone https://github.com/amplicalabls/content-watcher-service.git ``` -### Run a full e2e test +### Run a Full End-to-End Test -1. Run the following make command to spin up the entire stack: +1. Execute the following `make` command to deploy the entire stack: ```bash make test-start-services ``` - This will setup the following services: + This command will set up the following services: - - **Frequency:** A local instance of Frequency will be with default as instant sealing mode. - - **Redis:** A local instance of Redis will be spun up and configured to be used by content publishing and content watcher services. - - **Kubo IPFS:** A local instance of IPFS will be spun up and configured to be used for content publishing and retrieval. - - **Content Publishing API**: A local instance of the content publishing API will be used to publish content to IPFS and Frequency for content watcher tests. - - **Content Publishing Worker**: A local instance of the content publishing worker will be used to publish content to IPFS and Frequency for content watcher tests via dedicated processors. + - **Frequency:** A local instance of Frequency will be set up with the default instant sealing mode. + - **Redis:** A local instance of Redis will be initiated and configured for use by content publishing and content watcher services. + - **Kubo IPFS:** A local instance of IPFS will be initiated and configured for use in content publishing and retrieval. + - **Content Publishing API:** A local instance of the content publishing API will be utilized to publish content to IPFS and Frequency for content watcher tests. + - **Content Publishing Worker:** A local instance of the content publishing worker will be employed to publish content to IPFS and Frequency for content watcher tests via dedicated processors. - Following setup scenarios will be run while stack is brought up: + The following setup scenarios will be executed during the stack initialization: - - **Chain Setup Scenario**: A provider with MSA=1 will be created with some users accounts along with delegation to provider. Capacity will be staked to MSA=1 to enable provider to publish content on behalf of users. - - **DSNP Schemas**: DSNP schemas will be registered on Frequency. - - **Publish some example content**: Some example content will be published to IPFS and Frequency. Check out [Content Publishing BullBoard](http://0.0.0.0:3001/queues) to see the progress of content publishing. + - **Chain Setup Scenario:** A provider with MSA=1 will be created, with some user accounts, along with delegation to the provider. Capacity will be staked to MSA=1 to enable the provider to publish content on behalf of users. + - **DSNP Schemas:** DSNP schemas will be registered on Frequency. + - **Publish Some Example Content:** Example content will be published to IPFS and Frequency. Check the progress of content publishing at [Content Publishing BullBoard](http://0.0.0.0:3001/queues). -2. Run the following make command to run the content watcher tests: +2. Run the following `make` command to execute the content watcher tests: ```bash make test-e2e ``` -3. Alternatively, create .env file, run `nest start api` to start content watcher as standalone, register a webhook with content watcher using [swagger](http://0.0.0.0:3000/api/docs/swagger#) and try following scenarios: +3. Alternatively, create a `.env` file, run `nest start api` to start the content watcher as a standalone service, register a webhook with the content watcher using [swagger](http://0.0.0.0:3000/api/docs/swagger#), and try the following scenarios: - - **Reset scanner**: Reset scanner will reset the scanner to start from the beginning of the chain or whichever block is chose to start with. Upon successful parse respective announcement will be made to the webhook. - - **Put a search request**: Put a search request will put a search request on the queue. The request requires a start block and end block. Upon successful parse respective announcement will be made to the webhook. + - **Reset Scanner:** This action will reset the scanner to start from the beginning of the chain or whichever block is chosen to start with. Upon successful parsing, a respective announcement will be made to the webhook. + - **Put a Search Request:** This action will put a search request on the queue. The request requires a start block and end block. Upon successful parsing, a respective announcement will be made to the webhook. From d47ad96dcd664382d44c1d7b15ffe07eb1220324 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Mon, 15 Apr 2024 14:05:28 -0400 Subject: [PATCH 114/137] fix: remove unused ENVIRONMENT env var and related functions --- .../common/src/config/config.service.spec.ts | 6 --- .../libs/common/src/config/config.service.ts | 6 --- .../libs/common/src/config/env.config.ts | 4 -- .../libs/common/src/utils/dsnp.schema.ts | 50 ------------------- 4 files changed, 66 deletions(-) delete mode 100644 services/content-watcher/libs/common/src/utils/dsnp.schema.ts diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index d52ed80c..d86a7c3b 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -36,7 +36,6 @@ const setupConfigService = async (envObj: any): Promise => { describe('ContentWatcherConfigService', () => { const ALL_ENV: { [key: string]: string | undefined } = { - ENVIRONMENT: undefined, REDIS_URL: undefined, FREQUENCY_URL: undefined, STARTING_BLOCK: undefined, @@ -60,11 +59,6 @@ describe('ContentWatcherConfigService', () => { }); describe('invalid environment', () => { - it('missing environment should fail', async () => { - const { ENVIRONMENT: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ ...env })).rejects.toBeDefined(); - }); - it('missing redis url should fail', async () => { const { REDIS_URL: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ ...env })).rejects.toBeDefined(); diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index d6625423..d67b2342 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -4,10 +4,8 @@ https://docs.nestjs.com/providers#services import { Injectable, Logger } from '@nestjs/common'; import { ConfigService as NestConfigService } from '@nestjs/config'; -import { EnvironmentDto } from '..'; export interface ConfigEnvironmentVariables { - ENVIRONMENT: EnvironmentDto; IPFS_ENDPOINT: URL; IPFS_GATEWAY_URL: URL; IPFS_BASIC_AUTH_USER: string; @@ -33,10 +31,6 @@ export class ConfigService { this.logger = new Logger(this.constructor.name); } - public get environment(): EnvironmentDto { - return this.nestConfigService.get('ENVIRONMENT')!; - } - public get redisUrl(): URL { return this.nestConfigService.get('REDIS_URL')!; } diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 519cfbd6..74031acf 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -1,13 +1,9 @@ import Joi from 'joi'; import { ConfigModuleOptions } from '@nestjs/config'; -import { EnvironmentDto } from '..'; export const configModuleOptions: ConfigModuleOptions = { isGlobal: true, validationSchema: Joi.object({ - ENVIRONMENT: Joi.string() - .valid(...Object.values(EnvironmentDto)) - .required(), IPFS_ENDPOINT: Joi.string().uri().required(), IPFS_GATEWAY_URL: Joi.string().required(), // This is parse as string as the required format of this not a valid uri, check .env.template IPFS_BASIC_AUTH_USER: Joi.string().allow('').default(''), diff --git a/services/content-watcher/libs/common/src/utils/dsnp.schema.ts b/services/content-watcher/libs/common/src/utils/dsnp.schema.ts deleted file mode 100644 index 5af66a73..00000000 --- a/services/content-watcher/libs/common/src/utils/dsnp.schema.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { AnnouncementTypeDto, EnvironmentDto } from '../dtos/common.dto'; - -export namespace DsnpSchemas { - /** - * Map between announcement type and it's DSNP schema id for DEV environment - */ - const ANNOUNCEMENT_TO_SCHEMA_ID_DEV = new Map([ - [AnnouncementTypeDto.TOMBSTONE, 1], - [AnnouncementTypeDto.BROADCAST, 2], - [AnnouncementTypeDto.REPLY, 3], - [AnnouncementTypeDto.REACTION, 4], - [AnnouncementTypeDto.PROFILE, 5], - [AnnouncementTypeDto.UPDATE, 6], - ]); - /** - * Map between announcement type and it's DSNP schema id for ROCOCO environment - */ - const ANNOUNCEMENT_TO_SCHEMA_ID_ROCOCO = new Map([ - [AnnouncementTypeDto.TOMBSTONE, 1], - [AnnouncementTypeDto.BROADCAST, 2], - [AnnouncementTypeDto.REPLY, 3], - [AnnouncementTypeDto.REACTION, 4], - [AnnouncementTypeDto.PROFILE, 5], - [AnnouncementTypeDto.UPDATE, 6], - ]); - /** - * Map between announcement type and it's DSNP schema id for MAIN-NET environment - */ - const ANNOUNCEMENT_TO_SCHEMA_ID_MAIN_NET = new Map([ - [AnnouncementTypeDto.TOMBSTONE, 1], - [AnnouncementTypeDto.BROADCAST, 2], - [AnnouncementTypeDto.REPLY, 3], - [AnnouncementTypeDto.REACTION, 4], - [AnnouncementTypeDto.PROFILE, 6], - [AnnouncementTypeDto.UPDATE, 5], - ]); - /** - * Returns schema Id by environment and announcement type - */ - export function getSchemaId(environment: EnvironmentDto, announcementType: AnnouncementTypeDto): number { - switch (environment) { - case EnvironmentDto.MAIN_NET: - return ANNOUNCEMENT_TO_SCHEMA_ID_MAIN_NET.get(announcementType)!; - case EnvironmentDto.ROCOCO: - return ANNOUNCEMENT_TO_SCHEMA_ID_ROCOCO.get(announcementType)!; - default: - return ANNOUNCEMENT_TO_SCHEMA_ID_DEV.get(announcementType)!; - } - } -} From 2ff877bda8a645b78c7dee294b7026eb56477918 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Mon, 15 Apr 2024 14:19:16 -0400 Subject: [PATCH 115/137] fix: env cleanup - Remove unused environment variables and related configs/tests: HEALTH_CHECK_* - Rename WEB_HOOK_POST_MAX_RETRIES to WEBHOOK_FAILURE_THRESHOLD to match other Gateway services --- services/content-watcher/.env.cp.docker.dev | 3 - services/content-watcher/.env.dev | 5 +- services/content-watcher/env.template | 6 +- .../common/src/config/config.service.spec.ts | 37 +- .../libs/common/src/config/config.service.ts | 19 +- .../libs/common/src/config/env.config.ts | 5 +- services/content-watcher/package-lock.json | 547 ++++++------------ services/content-watcher/package.json | 6 +- 8 files changed, 185 insertions(+), 443 deletions(-) diff --git a/services/content-watcher/.env.cp.docker.dev b/services/content-watcher/.env.cp.docker.dev index 97db7904..1b382ff8 100644 --- a/services/content-watcher/.env.cp.docker.dev +++ b/services/content-watcher/.env.cp.docker.dev @@ -10,10 +10,7 @@ BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 PROVIDER_ACCOUNT_SEED_PHRASE="//Alice" WEBHOOK_FAILURE_THRESHOLD=3 -HEALTH_CHECK_SUCCESS_THRESHOLD=10 WEBHOOK_RETRY_INTERVAL_SECONDS=10 -HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 -HEALTH_CHECK_MAX_RETRIES=4 CAPACITY_LIMIT='{"type":"percentage", "value":80}' ENVIRONMENT="dev" API_PORT=3001 diff --git a/services/content-watcher/.env.dev b/services/content-watcher/.env.dev index aeb9795b..d4579e7f 100644 --- a/services/content-watcher/.env.dev +++ b/services/content-watcher/.env.dev @@ -13,9 +13,6 @@ STARTING_BLOCK="0" REDIS_URL=redis://127.0.0.1:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 -HEALTH_CHECK_SUCCESS_THRESHOLD=10 -HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 -HEALTH_CHECK_MAX_RETRIES=4 -WEB_HOOK_POST_MAX_RETRIES=4 +WEBHOOK_FAILURE_THRESHOLD=4 ENVIRONMENT="dev" API_PORT=3000 diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index e24a12b0..d5f3ae84 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -13,12 +13,8 @@ STARTING_BLOCK=0 REDIS_URL=redis://0.0.0.0:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 -HEALTH_CHECK_SUCCESS_THRESHOLD=10 -HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS=10 -HEALTH_CHECK_MAX_RETRIES=4 -WEB_HOOK_POST_MAX_RETRIES=4 +WEBHOOK_FAILURE_THRESHOLD=4 API_PORT=3000 # should be dev for e2e tests. Options [dev, rococo, mainnet] ENVIRONMENT=dev - diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index d86a7c3b..c9dc78fd 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -45,10 +45,7 @@ describe('ContentWatcherConfigService', () => { IPFS_BASIC_AUTH_SECRET: undefined, BLOCKCHAIN_SCAN_INTERVAL_MINUTES: undefined, QUEUE_HIGH_WATER: undefined, - HEALTH_CHECK_SUCCESS_THRESHOLD: undefined, - HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: undefined, - HEALTH_CHECK_MAX_RETRIES: undefined, - WEB_HOOK_POST_MAX_RETRIES: undefined, + WEBHOOK_FAILURE_THRESHOLD: undefined, API_PORT: undefined, }; @@ -93,26 +90,6 @@ describe('ContentWatcherConfigService', () => { await expect(setupConfigService({ QUEUE_HIGH_WATER: 'foo', ...env })).rejects.toBeDefined(); }); - it('invalid health check success threshold should fail', async () => { - const { HEALTH_CHECK_SUCCESS_THRESHOLD: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ HEALTH_CHECK_SUCCESS_THRESHOLD: -1, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ HEALTH_CHECK_SUCCESS_THRESHOLD: 0, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ HEALTH_CHECK_SUCCESS_THRESHOLD: 'foo', ...env })).rejects.toBeDefined(); - }); - - it('invalid health check max retry interval should fail', async () => { - const { HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: 0, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: 'foo', ...env })).rejects.toBeDefined(); - }); - - it('invalid health check max retry interval should fail', async () => { - const { HEALTH_CHECK_MAX_RETRIES: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRIES: -1, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ HEALTH_CHECK_MAX_RETRIES: 'foo', ...env })).rejects.toBeDefined(); - }); - it('invalid api port should fail', async () => { const { API_PORT: dummy, ...env } = ALL_ENV; await expect(setupConfigService({ API_PORT: -1, ...env })).rejects.toBeDefined(); @@ -145,18 +122,6 @@ describe('ContentWatcherConfigService', () => { expect(contentWatcherConfigService.getQueueHighWater()).toStrictEqual(parseInt(ALL_ENV.QUEUE_HIGH_WATER as string, 10)); }); - it('should get health check success threshold', () => { - expect(contentWatcherConfigService.getHealthCheckSuccessThreshold()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_SUCCESS_THRESHOLD as string, 10)); - }); - - it('should get health check max retry interval', () => { - expect(contentWatcherConfigService.getHealthCheckMaxRetryIntervalSeconds()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS as string, 10)); - }); - - it('should get health check max retries', () => { - expect(contentWatcherConfigService.getHealthCheckMaxRetries()).toStrictEqual(parseInt(ALL_ENV.HEALTH_CHECK_MAX_RETRIES as string, 10)); - }); - it('should get api port', () => { expect(contentWatcherConfigService.getApiPort()).toStrictEqual(parseInt(ALL_ENV.API_PORT as string, 10)); }); diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index d67b2342..231ba896 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -15,10 +15,7 @@ export interface ConfigEnvironmentVariables { STARTING_BLOCK: string; BLOCKCHAIN_SCAN_INTERVAL_MINUTES: number; QUEUE_HIGH_WATER: number; - HEALTH_CHECK_SUCCESS_THRESHOLD: number; - HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: number; - HEALTH_CHECK_MAX_RETRIES: number; - WEB_HOOK_POST_MAX_RETRIES: number; + WEBHOOK_FAILURE_THRESHOLD: number; API_PORT: number; } @@ -51,18 +48,6 @@ export class ConfigService { return this.nestConfigService.get('QUEUE_HIGH_WATER')!; } - public getHealthCheckSuccessThreshold(): number { - return this.nestConfigService.get('HEALTH_CHECK_SUCCESS_THRESHOLD')!; - } - - public getHealthCheckMaxRetryIntervalSeconds(): number { - return this.nestConfigService.get('HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS')!; - } - - public getHealthCheckMaxRetries(): number { - return this.nestConfigService.get('HEALTH_CHECK_MAX_RETRIES')!; - } - public getIpfsEndpoint(): string { return this.nestConfigService.get('IPFS_ENDPOINT')!; } @@ -92,6 +77,6 @@ export class ConfigService { } public getWebookMaxRetries(): number { - return this.nestConfigService.get('WEB_HOOK_POST_MAX_RETRIES')!; + return this.nestConfigService.get('WEBHOOK_FAILURE_THRESHOLD')!; } } diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 74031acf..3e47c157 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -15,10 +15,7 @@ export const configModuleOptions: ConfigModuleOptions = { .min(1) .default(3 * 60), QUEUE_HIGH_WATER: Joi.number().min(100).default(1000), - HEALTH_CHECK_SUCCESS_THRESHOLD: Joi.number().min(1).default(10), - HEALTH_CHECK_MAX_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(64), - HEALTH_CHECK_MAX_RETRIES: Joi.number().min(0).default(20), - WEB_HOOK_POST_MAX_RETRIES: Joi.number().min(0).default(3), + WEBHOOK_FAILURE_THRESHOLD: Joi.number().min(0).default(3), API_PORT: Joi.number().min(0).default(3000), }), }; diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 0ac921c6..8a7579e0 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -60,12 +60,12 @@ "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@types/time-constants": "^1.0.0", - "@typescript-eslint/parser": "^5.59.8", - "@typescript-eslint/typescript-estree": "5.59.8", + "@typescript-eslint/parser": "^7.7.0", + "@typescript-eslint/typescript-estree": "^7.7.0", "dotenv": "^16.3.1", "eslint": "^8.42.0", "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-prettier": "^8.8.0", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", @@ -1208,21 +1208,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, - "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -1238,9 +1240,10 @@ } }, "node_modules/@eslint/js": { - "version": "8.42.0", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, - "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -1266,12 +1269,13 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -1291,9 +1295,10 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause" + "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==", + "dev": true }, "node_modules/@ioredis/as-callback": { "version": "3.0.0", @@ -3268,8 +3273,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "license": "MIT" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -3342,9 +3348,10 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/semver": { - "version": "7.5.0", + "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": { @@ -3435,32 +3442,34 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.11", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz", + "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/type-utils": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/type-utils": "7.7.0", + "@typescript-eslint/utils": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -3468,98 +3477,27 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.11", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "5.59.11", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "5.59.11", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.9", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz", + "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "5.59.9", - "@typescript-eslint/types": "5.59.9", - "@typescript-eslint/typescript-estree": "5.59.9", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/typescript-estree": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", "debug": "^4.3.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.9", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.59.9", - "@typescript-eslint/visitor-keys": "5.59.9", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -3568,15 +3506,16 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.9", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz", + "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.9", - "@typescript-eslint/visitor-keys": "5.59.9" + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3584,65 +3523,26 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.11", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz", + "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/typescript-estree": "7.7.0", + "@typescript-eslint/utils": "7.7.0", "debug": "^4.3.4", - "tsutils": "^3.21.0" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "5.59.11", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.11", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -3650,29 +3550,13 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "5.59.11", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/types": { - "version": "5.59.9", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz", + "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==", "dev": true, - "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3680,20 +3564,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.8", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz", + "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "5.59.8", - "@typescript-eslint/visitor-keys": "5.59.8", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -3705,150 +3591,79 @@ } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { - "version": "5.59.8", + "node_modules/@typescript-eslint/typescript-estree/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", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.8", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.8", - "eslint-visitor-keys": "^3.3.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=16 || 14 >=14.17" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.11", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz", + "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==", "dev": true, - "license": "MIT", "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/typescript-estree": "7.7.0", + "semver": "^7.6.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.11", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.59.11", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.11", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/types": "5.59.11", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "eslint": "^8.56.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.9", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz", + "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==", "dev": true, - "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.9", - "eslint-visitor-keys": "^3.3.0" + "@typescript-eslint/types": "7.7.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || >=20.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 + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -4002,8 +3817,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "license": "MIT", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "bin": { "acorn": "bin/acorn" }, @@ -4021,8 +3837,9 @@ }, "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", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -5838,26 +5655,28 @@ } }, "node_modules/eslint": { - "version": "8.42.0", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.42.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@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.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "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", @@ -5867,7 +5686,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -5877,9 +5695,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -5919,17 +5736,17 @@ } }, "node_modules/eslint-config-airbnb-typescript": { - "version": "17.0.0", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-18.0.0.tgz", + "integrity": "sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg==", "dev": true, - "license": "MIT", "dependencies": { "eslint-config-airbnb-base": "^15.0.0" }, "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^5.13.0", - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.3" + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" } }, "node_modules/eslint-config-prettier": { @@ -6273,9 +6090,10 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", + "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", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -6284,9 +6102,10 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.2.0", + "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", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -6300,18 +6119,20 @@ }, "node_modules/eslint/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/espree": { - "version": "9.5.2", + "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", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -7091,9 +6912,10 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/globals": { - "version": "13.20.0", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -7176,12 +6998,6 @@ "version": "4.2.11", "license": "ISC" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/graphemer": { "version": "1.4.0", "dev": true, @@ -7407,9 +7223,10 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "5.2.4", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, - "license": "MIT", "engines": { "node": ">= 4" } @@ -9603,12 +9420,6 @@ "version": "1.4.0", "license": "MIT" }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/negotiator": { "version": "0.6.3", "license": "MIT", @@ -14537,9 +14348,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -15313,6 +15124,18 @@ "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, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-jest": { "version": "29.1.0", "dev": true, @@ -15519,25 +15342,6 @@ "version": "2.5.3", "license": "0BSD" }, - "node_modules/tsutils": { - "version": "3.21.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "dev": true, - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -15558,8 +15362,9 @@ }, "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)", "engines": { "node": ">=10" }, diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 8f505106..938aa877 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -88,12 +88,12 @@ "@types/node": "^20.3.1", "@types/supertest": "^2.0.12", "@types/time-constants": "^1.0.0", - "@typescript-eslint/parser": "^5.59.8", - "@typescript-eslint/typescript-estree": "5.59.8", + "@typescript-eslint/parser": "^7.7.0", + "@typescript-eslint/typescript-estree": "^7.7.0", "dotenv": "^16.3.1", "eslint": "^8.42.0", "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-airbnb-typescript": "^18.0.0", "eslint-config-prettier": "^8.8.0", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", From cf8eec0baae3f8717f1268bc171d67ac789f522a Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Mon, 15 Apr 2024 14:36:49 -0400 Subject: [PATCH 116/137] fix: sync env vars for gateway - make ConfigService consistent in using getters - add support for WEBHOOK_RETRY_INTERVAL_SECS in environment --- services/content-watcher/.env.dev | 1 + services/content-watcher/apps/api/src/main.ts | 5 +++- services/content-watcher/env.template | 1 + .../common/src/config/config.service.spec.ts | 7 +++--- .../libs/common/src/config/config.service.ts | 25 +++++++++++-------- .../libs/common/src/config/env.config.ts | 1 + .../libs/common/src/ipfs/ipfs.dsnp.ts | 2 +- .../libs/common/src/pubsub/pubsub.service.ts | 7 +++++- .../libs/common/src/scanner/scanner.ts | 6 ++--- .../libs/common/src/utils/ipfs.client.ts | 24 +++++++++--------- 10 files changed, 48 insertions(+), 31 deletions(-) diff --git a/services/content-watcher/.env.dev b/services/content-watcher/.env.dev index d4579e7f..e17168e3 100644 --- a/services/content-watcher/.env.dev +++ b/services/content-watcher/.env.dev @@ -14,5 +14,6 @@ REDIS_URL=redis://127.0.0.1:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 WEBHOOK_FAILURE_THRESHOLD=4 +WEBHOOK_RETRY_INTERVAL_SECONDS=10 ENVIRONMENT="dev" API_PORT=3000 diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index 5cc6a304..1929fa0c 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -3,6 +3,7 @@ import { Logger, ValidationPipe } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { ApiModule } from './api.module'; import { initSwagger } from '../../../libs/common/src/config/swagger_config'; +import { ConfigService } from '../../../libs/common/src/config/config.service'; const logger = new Logger('main'); @@ -14,6 +15,7 @@ async function bootstrap() { const app = await NestFactory.create(ApiModule, { logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error', 'warn', 'log'], }); + const configService = app.get(ConfigService); // Get event emitter & register a shutdown listener const eventEmitter = app.get(EventEmitter2); @@ -26,7 +28,8 @@ async function bootstrap() { app.enableShutdownHooks(); app.useGlobalPipes(new ValidationPipe()); await initSwagger(app, '/api/docs/swagger'); - await app.listen(process.env.API_PORT ?? 3000); + logger.log(`Listening on port ${configService.apiPort}`); + await app.listen(configService.apiPort); } catch (e) { await app.close(); logger.log('****** MAIN CATCH ********'); diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index d5f3ae84..23161627 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -14,6 +14,7 @@ REDIS_URL=redis://0.0.0.0:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 WEBHOOK_FAILURE_THRESHOLD=4 +WEBHOOK_RETRY_INTERVAL_SECONDS=10 API_PORT=3000 # should be dev for e2e tests. Options [dev, rococo, mainnet] diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index c9dc78fd..4cf39849 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -46,6 +46,7 @@ describe('ContentWatcherConfigService', () => { BLOCKCHAIN_SCAN_INTERVAL_MINUTES: undefined, QUEUE_HIGH_WATER: undefined, WEBHOOK_FAILURE_THRESHOLD: undefined, + WEBHOOK_RETRY_INTERVAL_SECONDS: undefined, API_PORT: undefined, }; @@ -115,15 +116,15 @@ describe('ContentWatcherConfigService', () => { }); it('should get scan interval', () => { - expect(contentWatcherConfigService.getBlockchainScanIntervalMinutes()).toStrictEqual(parseInt(ALL_ENV.BLOCKCHAIN_SCAN_INTERVAL_MINUTES as string, 10)); + expect(contentWatcherConfigService.blockchainScanIntervalMinutes).toStrictEqual(parseInt(ALL_ENV.BLOCKCHAIN_SCAN_INTERVAL_MINUTES as string, 10)); }); it('should get queue high water mark', () => { - expect(contentWatcherConfigService.getQueueHighWater()).toStrictEqual(parseInt(ALL_ENV.QUEUE_HIGH_WATER as string, 10)); + expect(contentWatcherConfigService.queueHighWater).toStrictEqual(parseInt(ALL_ENV.QUEUE_HIGH_WATER as string, 10)); }); it('should get api port', () => { - expect(contentWatcherConfigService.getApiPort()).toStrictEqual(parseInt(ALL_ENV.API_PORT as string, 10)); + expect(contentWatcherConfigService.apiPort).toStrictEqual(parseInt(ALL_ENV.API_PORT as string, 10)); }); }); }); diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index 231ba896..df3c8861 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -16,6 +16,7 @@ export interface ConfigEnvironmentVariables { BLOCKCHAIN_SCAN_INTERVAL_MINUTES: number; QUEUE_HIGH_WATER: number; WEBHOOK_FAILURE_THRESHOLD: number; + WEBHOOK_RETRY_INTERVAL_SECONDS: number; API_PORT: number; } @@ -40,43 +41,47 @@ export class ConfigService { return this.nestConfigService.get('STARTING_BLOCK')!; } - public getBlockchainScanIntervalMinutes(): number { + public get blockchainScanIntervalMinutes(): number { return this.nestConfigService.get('BLOCKCHAIN_SCAN_INTERVAL_MINUTES') ?? 1; } - public getQueueHighWater(): number { + public get queueHighWater(): number { return this.nestConfigService.get('QUEUE_HIGH_WATER')!; } - public getIpfsEndpoint(): string { + public get ipfsEndpoint(): string { return this.nestConfigService.get('IPFS_ENDPOINT')!; } - public getIpfsGatewayUrl(): string { + public get ipfsGatewayUrl(): string { return this.nestConfigService.get('IPFS_GATEWAY_URL')!; } - public getIpfsBasicAuthUser(): string { + public get ipfsBasicAuthUser(): string { return this.nestConfigService.get('IPFS_BASIC_AUTH_USER')!; } - public getIpfsBasicAuthSecret(): string { + public get ipfsBasicAuthSecret(): string { return this.nestConfigService.get('IPFS_BASIC_AUTH_SECRET')!; } - public getIpfsCidPlaceholder(cid): string { - const gatewayUrl = this.getIpfsGatewayUrl(); + public getIpfsCidPlaceholder(cid: string): string { + const gatewayUrl = this.ipfsGatewayUrl; if (!gatewayUrl || !gatewayUrl.includes('[CID]')) { return `https://ipfs.io/ipfs/${cid}`; } return gatewayUrl.replace('[CID]', cid); } - public getApiPort(): number { + public get apiPort(): number { return this.nestConfigService.get('API_PORT')!; } - public getWebookMaxRetries(): number { + public get webookMaxRetries(): number { return this.nestConfigService.get('WEBHOOK_FAILURE_THRESHOLD')!; } + + public get webhookRetryIntervalSeconds(): number { + return this.nestConfigService.get('WEBHOOK_RETRY_INTERVAL_SECONDS')!; + } } diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 3e47c157..3e2b3740 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -16,6 +16,7 @@ export const configModuleOptions: ConfigModuleOptions = { .default(3 * 60), QUEUE_HIGH_WATER: Joi.number().min(100).default(1000), WEBHOOK_FAILURE_THRESHOLD: Joi.number().min(0).default(3), + WEBHOOK_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(10), API_PORT: Joi.number().min(0).default(3000), }), }; diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index d378b73d..aa2513ab 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -204,7 +204,7 @@ export class IPFSContentProcessor extends BaseConsumer { } private async isQueueFull(queue: Queue): Promise { - const highWater = this.configService.getQueueHighWater(); + const highWater = this.configService.queueHighWater; const queueStats = await queue.getJobCounts(); const canAddJobs = queueStats.waiting + queueStats.active >= highWater; if (canAddJobs) { diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts index 1e451d5e..0d49313b 100644 --- a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts @@ -2,6 +2,7 @@ import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { Injectable, Logger } from '@nestjs/common'; import Redis from 'ioredis'; import axios from 'axios'; +import { MILLISECONDS_PER_SECOND } from 'time-constants'; import { EVENTS_TO_WATCH_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; import { AnnouncementResponse } from '../interfaces/announcement_response'; import { ConfigService } from '../config/config.service'; @@ -43,7 +44,7 @@ export class PubSubService { if (registrationsForMessageType) { registrationsForMessageType.urls.forEach(async (webhookUrl) => { let retries = 0; - while (retries < this.configService.getWebookMaxRetries()) { + while (retries < this.configService.webookMaxRetries) { try { this.logger.debug(`Sending announcement to webhook: ${webhookUrl}`); this.logger.debug(`Announcement: ${JSON.stringify(message)}`); @@ -55,6 +56,10 @@ export class PubSubService { this.logger.error(`Failed to send announcement to webhook: ${webhookUrl}`); this.logger.error(error); retries += 1; + // eslint-disable-next-line no-await-in-loop + await new Promise((r) => { + setTimeout(r, this.configService.webhookRetryIntervalSeconds * MILLISECONDS_PER_SECOND); + }); } } }); diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 787b350a..95e06144 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -50,7 +50,7 @@ export class ScannerService implements OnApplicationBootstrap { } private scheduleBlockchainScan() { - const scanInterval = this.configService.getBlockchainScanIntervalMinutes() * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; + const scanInterval = this.configService.blockchainScanIntervalMinutes * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; const interval = setInterval(() => this.scan(), scanInterval); this.schedulerRegistry.addInterval('blockchainScan', interval); @@ -106,7 +106,7 @@ export class ScannerService implements OnApplicationBootstrap { } this.logger.log(`Starting scan from block #${currentBlockNumber} (${latestBlockHash})`); - while (!this.paused && !latestBlockHash.isEmpty && queueSize < this.configService.getQueueHighWater()) { + while (!this.paused && !latestBlockHash.isEmpty && queueSize < this.configService.queueHighWater) { // eslint-disable-next-line no-await-in-loop const at = await this.blockchainService.apiPromise.at(latestBlockHash.toHex()); // eslint-disable-next-line no-await-in-loop @@ -128,7 +128,7 @@ export class ScannerService implements OnApplicationBootstrap { } if (latestBlockHash.isEmpty) { this.logger.log(`Scan reached end-of-chain at block ${lastScannedBlock - 1n}`); - } else if (queueSize > this.configService.getQueueHighWater()) { + } else if (queueSize > this.configService.queueHighWater) { this.logger.log('Queue soft limit reached; pausing scan until next iteration'); } } catch (err) { diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index 39c0d1a6..414d8f3e 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -28,15 +28,15 @@ export class IpfsService { } private async ipfsPinBuffer(filename: string, contentType: string, fileBuffer: Buffer): Promise { - const ipfsAdd = `${this.configService.getIpfsEndpoint()}/api/v0/add`; + const ipfsAdd = `${this.configService.ipfsEndpoint}/api/v0/add`; const form = new FormData(); form.append('file', fileBuffer, { filename, contentType, }); - const ipfsAuthUser = this.configService.getIpfsBasicAuthUser(); - const ipfsAuthSecret = this.configService.getIpfsBasicAuthSecret(); + const ipfsAuthUser = this.configService.ipfsBasicAuthUser; + const ipfsAuthSecret = this.configService.ipfsBasicAuthSecret; const ipfsAuth = ipfsAuthUser && ipfsAuthSecret ? `Basic ${Buffer.from(`${ipfsAuthUser}:${ipfsAuthSecret}`).toString('base64')}` : ''; const headers = { @@ -87,9 +87,9 @@ export class IpfsService { if (checkExistence && !(await this.isPinned(cid))) { return Promise.resolve(Buffer.alloc(0)); } - const ipfsGet = `${this.configService.getIpfsEndpoint()}/api/v0/cat?arg=${cid}`; - const ipfsAuthUser = this.configService.getIpfsBasicAuthUser(); - const ipfsAuthSecret = this.configService.getIpfsBasicAuthSecret(); + const ipfsGet = `${this.configService.ipfsEndpoint}/api/v0/cat?arg=${cid}`; + const ipfsAuthUser = this.configService.ipfsBasicAuthUser; + const ipfsAuthSecret = this.configService.ipfsBasicAuthSecret; const ipfsAuth = ipfsAuthUser && ipfsAuthSecret ? `Basic ${Buffer.from(`${ipfsAuthUser}:${ipfsAuthSecret}`).toString('base64')}` : ''; const headers = { @@ -107,9 +107,9 @@ export class IpfsService { public async isPinned(cid: string): Promise { const parsedCid = CID.parse(cid); const v0Cid = parsedCid.toV0().toString(); - const ipfsGet = `${this.configService.getIpfsEndpoint()}/api/v0/pin/ls?type=all&quiet=true&arg=${v0Cid}`; - const ipfsAuthUser = this.configService.getIpfsBasicAuthUser(); - const ipfsAuthSecret = this.configService.getIpfsBasicAuthSecret(); + const ipfsGet = `${this.configService.ipfsEndpoint}/api/v0/pin/ls?type=all&quiet=true&arg=${v0Cid}`; + const ipfsAuthUser = this.configService.ipfsBasicAuthUser; + const ipfsAuthSecret = this.configService.ipfsBasicAuthSecret; const ipfsAuth = ipfsAuthUser && ipfsAuthSecret ? `Basic ${Buffer.from(`${ipfsAuthUser}:${ipfsAuthSecret}`).toString('base64')}` : ''; const headers = { @@ -135,9 +135,9 @@ export class IpfsService { } public ipfsUrl(cid: string): string { - if (this.configService.getIpfsGatewayUrl().includes('[CID]')) { - return this.configService.getIpfsGatewayUrl().replace('[CID]', cid); + if (this.configService.ipfsGatewayUrl.includes('[CID]')) { + return this.configService.ipfsGatewayUrl.replace('[CID]', cid); } - return `${this.configService.getIpfsGatewayUrl()}/ipfs/${cid}`; + return `${this.configService.ipfsGatewayUrl}/ipfs/${cid}`; } } From 17518ef37eb145ed3106e330a5b233a39b706fa7 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Mon, 15 Apr 2024 15:11:08 -0400 Subject: [PATCH 117/137] feat: document env vars --- services/content-watcher/.env.dev | 2 +- services/content-watcher/ENVIRONMENT.md | 18 +++++++++ services/content-watcher/env.template | 38 ++++++++++++++----- .../libs/common/src/config/env.config.ts | 2 +- .../libs/common/src/ipfs/ipfs.dsnp.ts | 6 +-- 5 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 services/content-watcher/ENVIRONMENT.md diff --git a/services/content-watcher/.env.dev b/services/content-watcher/.env.dev index e17168e3..08d2d2c9 100644 --- a/services/content-watcher/.env.dev +++ b/services/content-watcher/.env.dev @@ -9,7 +9,7 @@ IPFS_BASIC_AUTH_SECRET="" IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" FREQUENCY_URL=ws://127.0.0.1:9944 -STARTING_BLOCK="0" +STARTING_BLOCK=1 REDIS_URL=redis://127.0.0.1:6379 BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 QUEUE_HIGH_WATER=1000 diff --git a/services/content-watcher/ENVIRONMENT.md b/services/content-watcher/ENVIRONMENT.md new file mode 100644 index 00000000..df3811cd --- /dev/null +++ b/services/content-watcher/ENVIRONMENT.md @@ -0,0 +1,18 @@ +# Environment Variables + +This application recognizes the following environment variables: + +| Name | Description | Range/Type | Required? | Default | +| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------: | :----------: | :-----: | +|`API_PORT` | HTTP port that the application listens on | 1025 - 65535 | | 3000 | +|`BLOCKCHAIN_SCAN_INTERVAL_MINUTES` | How many minutes to delay between successive scans of the chain for new accounts (after end of chain is reached) | > 0 | | 180 | +|`FREQUENCY_URL` | Blockchain node address | http(s): or ws(s): URL | Y | | +|`IPFS_BASIC_AUTH_SECRET`|If using Infura, put auth token here, or leave blank for Kubo RPC|string|N|blank| +|`IPFS_BASIC_AUTH_USER`|If using Infura, put Project ID here, or leave blank for Kubo RPC|string|N|blank| +|`IPFS_ENDPOINT`|URL to IPFS endpoint|URL|Y|| +|`IPFS_GATEWAY_URL`|IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID|URL template|Y|| +|`QUEUE_HIGH_WATER` | Max number of jobs allowed on the 'graphUpdateQueue' before blockchain scan will be paused to allow queue to drain | >= 100 | | 1000 | +|`REDIS_URL` | Connection URL for Redis | URL | Y | +|`STARTING_BLOCK`|Block number from which the service will start scanning the chain|> 0||1| +|`WEBHOOK_FAILURE_THRESHOLD` | Number of failures allowing in the provider webhook before the service is marked down | > 0 | | 3 | +|`WEBHOOK_RETRY_INTERVAL_SECONDS` | Number of seconds between provider webhook retry attempts when failing | > 0 | | 10 | diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index 23161627..0422107e 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -1,21 +1,41 @@ # Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development + +# URL to IPFS endpoint # IPFS_ENDPOINT="https://ipfs.infura.io:5001" -# IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" -# IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" -# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" IPFS_ENDPOINT="http://127.0.0.1:5001" -IPFS_BASIC_AUTH_USER="" -IPFS_BASIC_AUTH_SECRET="" + +# If using Infura, put Project ID here, or leave blank for Kubo RPC +# IPFS_BASIC_AUTH_USER= + +# If using Infura, put auth token here, or leave blank for Kubo RPC +IPFS_BASIC_AUTH_SECRET= + +# IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID +# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" +# Blockchain node address FREQUENCY_URL=ws://0.0.0.0:9944 -STARTING_BLOCK=0 + +# Block number from which the service will start scanning the chain +STARTING_BLOCK=1 + +# Redis URL REDIS_URL=redis://0.0.0.0:6379 + +# How many minutes to delay between successive scans of the chain +# for new accounts (after end of chain is reached) BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 + +# Max number of jobs allowed on the queue before +# blockchain scan will be paused to allow queue to drain QUEUE_HIGH_WATER=1000 + +# Number of retry attempts if a registered webhook call fails WEBHOOK_FAILURE_THRESHOLD=4 + +# Number of seconds between webhook retry attempts when failing WEBHOOK_RETRY_INTERVAL_SECONDS=10 -API_PORT=3000 -# should be dev for e2e tests. Options [dev, rococo, mainnet] -ENVIRONMENT=dev +# Port that the application REST endpoints listen on +API_PORT=3000 diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 3e2b3740..c6ecb3ac 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -10,7 +10,7 @@ export const configModuleOptions: ConfigModuleOptions = { IPFS_BASIC_AUTH_SECRET: Joi.string().allow('').default(''), REDIS_URL: Joi.string().uri().required(), FREQUENCY_URL: Joi.string().uri().required(), - STARTING_BLOCK: Joi.string().default('0'), + STARTING_BLOCK: Joi.number().min(1).default(1), BLOCKCHAIN_SCAN_INTERVAL_MINUTES: Joi.number() .min(1) .default(3 * 60), diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index aa2513ab..d6210063 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -206,11 +206,11 @@ export class IPFSContentProcessor extends BaseConsumer { private async isQueueFull(queue: Queue): Promise { const highWater = this.configService.queueHighWater; const queueStats = await queue.getJobCounts(); - const canAddJobs = queueStats.waiting + queueStats.active >= highWater; - if (canAddJobs) { + const queueIsFull = queueStats.waiting + queueStats.active >= highWater; + if (queueIsFull) { this.logger.log(`Queue ${queue.name} is full`); throw new Error(`Queue ${queue.name} is full`); } - return canAddJobs; + return queueIsFull; } } From 3f5f4d8522ab6b830c6cf312838dcda44f1dd752 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Mon, 15 Apr 2024 15:14:09 -0400 Subject: [PATCH 118/137] feat: update README --- services/content-watcher/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index 3b984da1..4d943c29 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -29,6 +29,9 @@ Follow these steps to set up and run Content Watcher: git clone https://github.com/amplicalabls/content-watcher-service.git ``` +### Configure the app +The application is configurable by means of the environment. Default environment files are provided with sane values. The available environment configuration variables are document [here](./ENVIRONMENT.md), and a sample file is located [here](./env.template) + ### Run a Full End-to-End Test 1. Execute the following `make` command to deploy the entire stack: From c277b3f7e3314388d1cd2f398cd53bc78edf4764 Mon Sep 17 00:00:00 2001 From: Wil Wade Date: Tue, 23 Apr 2024 15:20:49 -0400 Subject: [PATCH 119/137] Update publishing pages (#38) - Adds Dependabot - Updates actions - Update github pages publishing See Test deploy: https://amplicalabs.github.io/content-watcher-service/ Closes #37 --- .../content-watcher/.github/dependabot.yml | 11 + .../.github/workflows/build.yml | 51 +- .../.github/workflows/deploy-gh-pages.yaml | 51 + .../.github/workflows/release.yml | 16 +- services/content-watcher/.gitignore | 1 + services/content-watcher/docs/.gitkeep | 0 services/content-watcher/package-lock.json | 3456 ----------------- services/content-watcher/package.json | 26 +- 8 files changed, 92 insertions(+), 3520 deletions(-) create mode 100644 services/content-watcher/.github/dependabot.yml create mode 100644 services/content-watcher/.github/workflows/deploy-gh-pages.yaml create mode 100644 services/content-watcher/docs/.gitkeep diff --git a/services/content-watcher/.github/dependabot.yml b/services/content-watcher/.github/dependabot.yml new file mode 100644 index 00000000..d91a92ad --- /dev/null +++ b/services/content-watcher/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: 'npm' # See documentation for possible values + directory: '/' # Location of package manifests + schedule: + interval: 'weekly' diff --git a/services/content-watcher/.github/workflows/build.yml b/services/content-watcher/.github/workflows/build.yml index 434637e0..b69ad702 100644 --- a/services/content-watcher/.github/workflows/build.yml +++ b/services/content-watcher/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build And Test ContentWatcher Service +name: Build And Test Content Watcher Service concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: true @@ -11,29 +11,31 @@ on: - main jobs: - build_Nest_js: + build: + name: 'Build' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'npm' registry-url: 'https://registry.npmjs.org' cache-dependency-path: package-lock.json - name: Install dependencies run: npm ci - - name: Build Nest.js + - name: Build NestJS run: npm run build test_jest: + name: 'Test' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'npm' registry-url: 'https://registry.npmjs.org' cache-dependency-path: package-lock.json @@ -42,13 +44,14 @@ jobs: - name: Run Jest run: npm run test check_licenses: + name: 'Dependency License Check' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'npm' registry-url: 'https://registry.npmjs.org' cache-dependency-path: package-lock.json @@ -57,27 +60,3 @@ jobs: - name: License Check # List all the licenses and error out if it is not one of the supported licenses run: npx license-report --fields=name --fields=licenseType | jq 'map(select(.licenseType | IN("MIT", "Apache-2.0", "ISC", "BSD-3-Clause", "BSD-2-Clause", "(Apache-2.0 AND MIT)") | not)) | if length == 0 then halt else halt_error(1) end' - - publish: - runs-on: ubuntu-latest - needs: [build_Nest_js, test_jest, check_licenses] - steps: - - uses: actions/checkout@v3 - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'npm' - registry-url: 'https://registry.npmjs.org' - cache-dependency-path: package-lock.json - - name: Install dependencies - run: npm ci - - name: Generate Swagger UI - run: npm run generate-swagger-ui - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Publish generated swagger.html to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs diff --git a/services/content-watcher/.github/workflows/deploy-gh-pages.yaml b/services/content-watcher/.github/workflows/deploy-gh-pages.yaml new file mode 100644 index 00000000..660b20dd --- /dev/null +++ b/services/content-watcher/.github/workflows/deploy-gh-pages.yaml @@ -0,0 +1,51 @@ +name: Build and Publish OpenAPI UI to Pages +concurrency: + group: ${{github.workflow}}-${{github.ref}} + cancel-in-progress: true +on: + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + name: 'Build Pages Artifact' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Generate Swagger UI + run: npm run generate-swagger-ui + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v4 + + - name: Publish generated swagger.html to GitHub Pages + uses: actions/upload-pages-artifact@v3 + with: + path: ./docs + + # Deployment job + deploy: + name: 'Deploy to Pages' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/services/content-watcher/.github/workflows/release.yml b/services/content-watcher/.github/workflows/release.yml index 665af5d6..9d95aa4f 100644 --- a/services/content-watcher/.github/workflows/release.yml +++ b/services/content-watcher/.github/workflows/release.yml @@ -25,7 +25,7 @@ env: jobs: build-and-publish-container-image: name: Build and publish container image - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest container: ghcr.io/libertydsnp/frequency/ci-base-image steps: - name: Validate Version Tag @@ -42,12 +42,12 @@ jobs: fi echo "valid-version=true" >> $GITHUB_OUTPUT - name: Check Out Repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{env.NEW_RELEASE_TAG_FROM_UI}} - name: Set up tags for cp image id: cp-tags - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: flavor: | latest=auto @@ -57,22 +57,22 @@ jobs: tags: | type=semver,pattern={{version}} - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 with: platforms: | linux/amd64 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{secrets.DOCKERHUB_USERNAME_FC}} password: ${{secrets.DOCKERHUB_TOKEN_FC}} - name: Build and Push content-watcher-service Image - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . platforms: linux/amd64 push: ${{env.TEST_RUN != 'true'}} file: ./Dockerfile - tags: ${{ steps.cp-tags.outputs.tags }} \ No newline at end of file + tags: ${{ steps.cp-tags.outputs.tags }} diff --git a/services/content-watcher/.gitignore b/services/content-watcher/.gitignore index b859bff7..f663a812 100644 --- a/services/content-watcher/.gitignore +++ b/services/content-watcher/.gitignore @@ -4,3 +4,4 @@ dist .vscode coverage .idea +docs/*.html diff --git a/services/content-watcher/docs/.gitkeep b/services/content-watcher/docs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 8a7579e0..8fa3abb3 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -78,7 +78,6 @@ "license-report": "^6.4.0", "nock": "^13.3.8", "prettier": "^3.0.2", - "redoc-cli": "^0.13.21", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "trace-unhandled": "^2.0.1", @@ -10513,3461 +10512,6 @@ "node": ">=4" } }, - "node_modules/redoc-cli": { - "version": "0.13.21", - "resolved": "https://registry.npmjs.org/redoc-cli/-/redoc-cli-0.13.21.tgz", - "integrity": "sha512-pjuPf0HkKqo9qtoHxMK4x5dhC/lJ08O0hO0rJISbSRCf19bPBjQ5lb2mHRu9j6vypTMltyaLtFIfVNveuyF5fQ==", - "dev": true, - "hasShrinkwrap": true, - "dependencies": { - "boxen": "5.1.2", - "chokidar": "^3.5.1", - "handlebars": "^4.7.7", - "mkdirp": "^1.0.4", - "mobx": "^6.3.2", - "node-libs-browser": "^2.2.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "redoc": "2.0.0", - "styled-components": "^5.3.0", - "yargs": "^17.3.1" - }, - "bin": { - "redoc-cli": "index.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/redoc-cli/node_modules/@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.12.13" - } - }, - "node_modules/redoc-cli/node_modules/@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.14.2", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/redoc-cli/node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/redoc-cli/node_modules/@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" - } - }, - "node_modules/redoc-cli/node_modules/@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/redoc-cli/node_modules/@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.13.12" - } - }, - "node_modules/redoc-cli/node_modules/@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.13" - } - }, - "node_modules/redoc-cli/node_modules/@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/redoc-cli/node_modules/@babel/parser": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", - "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/redoc-cli/node_modules/@babel/runtime": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", - "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/redoc-cli/node_modules/@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "node_modules/redoc-cli/node_modules/@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "node_modules/redoc-cli/node_modules/@babel/types": { - "version": "7.14.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", - "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" - } - }, - "node_modules/redoc-cli/node_modules/@emotion/is-prop-valid": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", - "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", - "dev": true, - "dependencies": { - "@emotion/memoize": "0.7.4" - } - }, - "node_modules/redoc-cli/node_modules/@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/@exodus/schemasafe": { - "version": "1.0.0-rc.6", - "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.0.0-rc.6.tgz", - "integrity": "sha512-dDnQizD94EdBwEj/fh3zPRa/HWCS9O5au2PuHhZBbuM3xWHxuaKzPBOEWze7Nn0xW68MIpZ7Xdyn1CoCpjKCuQ==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/redoc-cli/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/redoc-cli/node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/redoc-cli/node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/redoc-cli/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/redoc-cli/node_modules/@redocly/ajv": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", - "integrity": "sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/redoc-cli/node_modules/@redocly/openapi-core": { - "version": "1.0.0-beta.105", - "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.105.tgz", - "integrity": "sha512-8uYDMcqBOPhFgjRlg5uetW/E2uTVVRpk+YsJhaH78ZNuzBkQP5Waw5s8P8ym6myvHs5me8l5AdniY/ePLMT5xg==", - "dev": true, - "dependencies": { - "@redocly/ajv": "^8.6.4", - "@types/node": "^14.11.8", - "colorette": "^1.2.0", - "js-levenshtein": "^1.1.6", - "js-yaml": "^4.1.0", - "lodash.isequal": "^4.5.0", - "minimatch": "^5.0.1", - "node-fetch": "^2.6.1", - "pluralize": "^8.0.0", - "yaml-ast-parser": "0.0.43" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/redoc-cli/node_modules/@redocly/openapi-core/node_modules/@types/node": { - "version": "14.18.22", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.22.tgz", - "integrity": "sha512-qzaYbXVzin6EPjghf/hTdIbnVW1ErMx8rPzwRNJhlbyJhu2SyqlvjGOY/tbUt6VFyzg56lROcOeSQRInpt63Yw==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/@types/eslint": { - "version": "8.4.6", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", - "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==", - "dev": true, - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/redoc-cli/node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/redoc-cli/node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/@types/mkdirp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.1.tgz", - "integrity": "sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==", - "extraneous": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/redoc-cli/node_modules/@types/node": { - "version": "15.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz", - "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "peer": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "peer": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/redoc-cli/node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "peer": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/redoc-cli/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, - "peer": true - }, - "node_modules/redoc-cli/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, - "peer": true - }, - "node_modules/redoc-cli/node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/redoc-cli/node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peer": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/redoc-cli/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, - "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/redoc-cli/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, - "peer": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/redoc-cli/node_modules/ajv/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, - "peer": true - }, - "node_modules/redoc-cli/node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/redoc-cli/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/redoc-cli/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, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/redoc-cli/node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/redoc-cli/node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/redoc-cli/node_modules/babel-plugin-styled-components": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz", - "integrity": "sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-module-imports": "^7.0.0", - "babel-plugin-syntax-jsx": "^6.18.0", - "lodash": "^4.17.11" - }, - "peerDependencies": { - "styled-components": ">= 2" - } - }, - "node_modules/redoc-cli/node_modules/babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", - "dev": true - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/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==", - "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" - } - ] - }, - "node_modules/redoc-cli/node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/redoc-cli/node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/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/redoc-cli/node_modules/boxen/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/redoc-cli/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, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/redoc-cli/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redoc-cli/node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/redoc-cli/node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/redoc-cli/node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/redoc-cli/node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/redoc-cli/node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/redoc-cli/node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redoc-cli/node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/redoc-cli/node_modules/browserslist": { - "version": "4.21.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.3.tgz", - "integrity": "sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001370", - "electron-to-chromium": "^1.4.202", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.5" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/redoc-cli/node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "node_modules/redoc-cli/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, - "peer": true - }, - "node_modules/redoc-cli/node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/redoc-cli/node_modules/camelize": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/caniuse-lite": { - "version": "1.0.30001390", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz", - "integrity": "sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ], - "peer": true - }, - "node_modules/redoc-cli/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, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "node_modules/redoc-cli/node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/redoc-cli/node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/redoc-cli/node_modules/classnames": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", - "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/redoc-cli/node_modules/clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/redoc-cli/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, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/redoc-cli/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "dev": true - }, - "node_modules/redoc-cli/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, - "peer": true - }, - "node_modules/redoc-cli/node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/core-js": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.0.tgz", - "integrity": "sha512-CVU1xvJEfJGhyCpBrzzzU1kjCfgsGUxhEvwUV2e/cOedYWHdmluamx+knDnmhqALddMG16fZvIqvs9aijsHHaA==", - "dev": true, - "hasInstallScript": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/redoc-cli/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/redoc-cli/node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/redoc-cli/node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/redoc-cli/node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/redoc-cli/node_modules/css-color-keywords": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/node_modules/css-to-react-native": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", - "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", - "dev": true, - "dependencies": { - "camelize": "^1.0.0", - "css-color-keywords": "^1.0.0", - "postcss-value-parser": "^4.0.2" - } - }, - "node_modules/redoc-cli/node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/redoc-cli/node_modules/decko": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decko/-/decko-1.2.0.tgz", - "integrity": "sha1-/UPHNelnuAEzBohKVvvmZZlraBc=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/redoc-cli/node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/redoc-cli/node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/redoc-cli/node_modules/dompurify": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.5.tgz", - "integrity": "sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/electron-to-chromium": { - "version": "1.4.242", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.242.tgz", - "integrity": "sha512-nPdgMWtjjWGCtreW/2adkrB2jyHjClo9PtVhR6rW+oxa4E4Wom642Tn+5LslHP3XPL5MCpkn5/UEY60EXylNeQ==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/redoc-cli/node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/redoc-cli/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/redoc-cli/node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/redoc-cli/node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/redoc-cli/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": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/redoc-cli/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, - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/redoc-cli/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, - "peer": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/redoc-cli/node_modules/esrecurse/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, - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/redoc-cli/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/redoc-cli/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/redoc-cli/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, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/redoc-cli/node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/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, - "peer": true - }, - "node_modules/redoc-cli/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==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redoc-cli/node_modules/foreach": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", - "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/redoc-cli/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/redoc-cli/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, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redoc-cli/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, - "peer": true - }, - "node_modules/redoc-cli/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, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/node_modules/graceful-fs": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", - "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/redoc-cli/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redoc-cli/node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/redoc-cli/node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/redoc-cli/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==", - "dev": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/redoc-cli/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==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/http2-client": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", - "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "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" - } - ] - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/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, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redoc-cli/node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/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/redoc-cli/node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/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, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/redoc-cli/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, - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/redoc-cli/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, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/redoc-cli/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, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/redoc-cli/node_modules/js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/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, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/redoc-cli/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/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==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/json-pointer": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", - "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", - "dev": true, - "dependencies": { - "foreach": "^2.0.4" - } - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/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, - "peer": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/redoc-cli/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/redoc-cli/node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true - }, - "node_modules/redoc-cli/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, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/redoc-cli/node_modules/lunr": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", - "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha1-GA8fnr74sOY45BZq1S24eb6y/8U=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/marked": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.15.tgz", - "integrity": "sha512-esX5lPdTfG4p8LDkv+obbRCyOKzB+820ZZyMOXJZygZBHrH9b3xXR64X4kT3sPe9Nx8qQXbmcz6kFSMt4Nfk6Q==", - "dev": true, - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 12" - } - }, - "node_modules/redoc-cli/node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/redoc-cli/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, - "peer": true - }, - "node_modules/redoc-cli/node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/redoc-cli/node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/redoc-cli/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==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/redoc-cli/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==", - "dev": true, - "peer": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/minimatch": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", - "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/redoc-cli/node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/redoc-cli/node_modules/mobx": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.3.2.tgz", - "integrity": "sha512-xGPM9dIE1qkK9Nrhevp0gzpsmELKU4MFUJRORW/jqxVFIHHWIoQrjDjL8vkwoJYY3C2CeVJqgvl38hgKTalTWg==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - } - }, - "node_modules/redoc-cli/node_modules/mobx-react": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-7.2.1.tgz", - "integrity": "sha512-LZS99KFLn75VWDXPdRJhILzVQ7qLcRjQbzkK+wVs0Qg4kWw5hOI2USp7tmu+9zP9KYsVBmKyx2k/8cTTBfsymw==", - "dev": true, - "dependencies": { - "mobx-react-lite": "^3.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - }, - "peerDependencies": { - "mobx": "^6.1.0", - "react": "^16.8.0 || ^17" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/redoc-cli/node_modules/mobx-react-lite": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-3.2.3.tgz", - "integrity": "sha512-7exWp1FV0M9dP08H9PIeHlJqDw4IdkQVRMfLYaZFMmlbzSS6ZU6p/kx392KN+rVf81hH3IQYewvRGQ70oiwmbw==", - "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - }, - "peerDependencies": { - "mobx": "^6.1.0", - "react": "^16.8.0 || ^17" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/redoc-cli/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/redoc-cli/node_modules/node-fetch-h2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", - "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", - "dev": true, - "dependencies": { - "http2-client": "^1.2.5" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/redoc-cli/node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/redoc-cli/node_modules/node-readfiles": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", - "integrity": "sha1-271K8SE04uY1wkXvk//Pb2BnOl0=", - "dev": true, - "dependencies": { - "es6-promise": "^3.2.1" - } - }, - "node_modules/redoc-cli/node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/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, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/node_modules/oas-kit-common": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", - "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", - "dev": true, - "dependencies": { - "fast-safe-stringify": "^2.0.7" - } - }, - "node_modules/redoc-cli/node_modules/oas-linter": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz", - "integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==", - "dev": true, - "dependencies": { - "@exodus/schemasafe": "^1.0.0-rc.2", - "should": "^13.2.1", - "yaml": "^1.10.0" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/redoc-cli/node_modules/oas-resolver": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz", - "integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==", - "dev": true, - "dependencies": { - "node-fetch-h2": "^2.3.0", - "oas-kit-common": "^1.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "bin": { - "resolve": "resolve.js" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/redoc-cli/node_modules/oas-schema-walker": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz", - "integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==", - "dev": true, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/redoc-cli/node_modules/oas-validator": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz", - "integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==", - "dev": true, - "dependencies": { - "call-me-maybe": "^1.0.1", - "oas-kit-common": "^1.0.8", - "oas-linter": "^3.2.2", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "reftools": "^1.1.9", - "should": "^13.2.1", - "yaml": "^1.10.0" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/redoc-cli/node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/node_modules/openapi-sampler": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/openapi-sampler/-/openapi-sampler-1.3.0.tgz", - "integrity": "sha512-2QfjK1oM9Sv0q82Ae1RrUe3yfFmAyjF548+6eAeb+h/cL1Uj51TW4UezraBEvwEdzoBgfo4AaTLVFGTKj+yYDw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.7", - "json-pointer": "0.6.2" - } - }, - "node_modules/redoc-cli/node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/redoc-cli/node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/redoc-cli/node_modules/perfect-scrollbar": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", - "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/redoc-cli/node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/node_modules/polished": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/polished/-/polished-4.1.4.tgz", - "integrity": "sha512-Nq5Mbza+Auo7N3sQb1QMFaQiDO+4UexWuSGR7Cjb4Sw11SZIJcrrFtiZ+L0jT9MBsUsxDboHVASbCLbE1rnECg==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.16.7" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/redoc-cli/node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/prismjs": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", - "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/redoc-cli/node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/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, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/redoc-cli/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==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/redoc-cli/node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/redoc-cli/node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/redoc-cli/node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/redoc-cli/node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/redoc-cli/node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, - "node_modules/redoc-cli/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true, - "peer": true - }, - "node_modules/redoc-cli/node_modules/react-tabs": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-3.2.3.tgz", - "integrity": "sha512-jx325RhRVnS9DdFbeF511z0T0WEqEoMl1uCE3LoZ6VaZZm7ytatxbum0B8bCTmaiV0KsU+4TtLGTGevCic7SWg==", - "dev": true, - "dependencies": { - "clsx": "^1.1.0", - "prop-types": "^15.5.0" - }, - "peerDependencies": { - "react": "^16.3.0 || ^17.0.0-0" - } - }, - "node_modules/redoc-cli/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/redoc-cli/node_modules/readable-stream/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 - }, - "node_modules/redoc-cli/node_modules/readable-stream/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, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/redoc-cli/node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/redoc-cli/node_modules/redoc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redoc/-/redoc-2.0.0.tgz", - "integrity": "sha512-rU8iLdAkT89ywOkYk66Mr+IofqaMASlRvTew0dJvopCORMIPUcPMxjlJbJNC6wsn2vvMnpUFLQ/0ISDWn9BWag==", - "dev": true, - "dependencies": { - "@redocly/openapi-core": "^1.0.0-beta.104", - "classnames": "^2.3.1", - "decko": "^1.2.0", - "dompurify": "^2.2.8", - "eventemitter3": "^4.0.7", - "json-pointer": "^0.6.2", - "lunr": "^2.3.9", - "mark.js": "^8.11.1", - "marked": "^4.0.15", - "mobx-react": "^7.2.0", - "openapi-sampler": "^1.3.0", - "path-browserify": "^1.0.1", - "perfect-scrollbar": "^1.5.5", - "polished": "^4.1.3", - "prismjs": "^1.27.0", - "prop-types": "^15.7.2", - "react-tabs": "^3.2.2", - "slugify": "~1.4.7", - "stickyfill": "^1.1.1", - "style-loader": "^3.3.1", - "swagger2openapi": "^7.0.6", - "url-template": "^2.0.8" - }, - "engines": { - "node": ">=6.9", - "npm": ">=3.0.0" - }, - "peerDependencies": { - "core-js": "^3.1.4", - "mobx": "^6.0.4", - "react": "^16.8.4 || ^17.0.0", - "react-dom": "^16.8.4 || ^17.0.0", - "styled-components": "^4.1.1 || ^5.1.1" - } - }, - "node_modules/redoc-cli/node_modules/redoc/node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/reftools": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz", - "integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==", - "dev": true, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/redoc-cli/node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/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, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/redoc-cli/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" - } - ] - }, - "node_modules/redoc-cli/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 - }, - "node_modules/redoc-cli/node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "dev": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "node_modules/redoc-cli/node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "peer": true, - "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/redoc-cli/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "peer": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/redoc-cli/node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/redoc-cli/node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/should": { - "version": "13.2.3", - "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", - "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", - "dev": true, - "dependencies": { - "should-equal": "^2.0.0", - "should-format": "^3.0.3", - "should-type": "^1.4.0", - "should-type-adaptors": "^1.0.1", - "should-util": "^1.0.0" - } - }, - "node_modules/redoc-cli/node_modules/should-equal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", - "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", - "dev": true, - "dependencies": { - "should-type": "^1.4.0" - } - }, - "node_modules/redoc-cli/node_modules/should-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", - "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", - "dev": true, - "dependencies": { - "should-type": "^1.3.0", - "should-type-adaptors": "^1.0.1" - } - }, - "node_modules/redoc-cli/node_modules/should-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/should-type-adaptors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", - "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", - "dev": true, - "dependencies": { - "should-type": "^1.3.0", - "should-util": "^1.0.0" - } - }, - "node_modules/redoc-cli/node_modules/should-util": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/slugify": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.4.7.tgz", - "integrity": "sha512-tf+h5W1IrjNm/9rKKj0JU2MDMruiopx0jjVA5zCdBtcGjfp0+c5rHw/zADLC3IeKlGHtVbHtpfzvYA0OYT+HKg==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/redoc-cli/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, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/redoc-cli/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, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/redoc-cli/node_modules/stickyfill": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stickyfill/-/stickyfill-1.1.1.tgz", - "integrity": "sha1-OUE/7p0CXHSn5ZzuyyN4TMDxfwI=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/redoc-cli/node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/redoc-cli/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, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/redoc-cli/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/redoc-cli/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/redoc-cli/node_modules/style-loader": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", - "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", - "dev": true, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/redoc-cli/node_modules/styled-components": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.0.tgz", - "integrity": "sha512-bPJKwZCHjJPf/hwTJl6TbkSZg/3evha+XPEizrZUGb535jLImwDUdjTNxXqjjaASt2M4qO4AVfoHJNe3XB/tpQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/traverse": "^7.4.5", - "@emotion/is-prop-valid": "^0.8.8", - "@emotion/stylis": "^0.8.4", - "@emotion/unitless": "^0.7.4", - "babel-plugin-styled-components": ">= 1.12.0", - "css-to-react-native": "^3.0.0", - "hoist-non-react-statics": "^3.0.0", - "shallowequal": "^1.1.0", - "supports-color": "^5.5.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/styled-components" - }, - "peerDependencies": { - "react": ">= 16.8.0", - "react-dom": ">= 16.8.0", - "react-is": ">= 16.8.0" - } - }, - "node_modules/redoc-cli/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, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/node_modules/swagger2openapi": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", - "integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==", - "dev": true, - "dependencies": { - "call-me-maybe": "^1.0.1", - "node-fetch": "^2.6.1", - "node-fetch-h2": "^2.3.0", - "node-readfiles": "^0.2.0", - "oas-kit-common": "^1.0.8", - "oas-resolver": "^2.5.6", - "oas-schema-walker": "^1.1.5", - "oas-validator": "^5.0.8", - "reftools": "^1.1.9", - "yaml": "^1.10.0", - "yargs": "^17.0.1" - }, - "bin": { - "boast": "boast.js", - "oas-validate": "oas-validate.js", - "swagger2openapi": "swagger2openapi.js" - }, - "funding": { - "url": "https://github.com/Mermade/oas-kit?sponsor=1" - } - }, - "node_modules/redoc-cli/node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/redoc-cli/node_modules/terser": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz", - "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/redoc-cli/node_modules/terser-webpack-plugin": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", - "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", - "dev": true, - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.14", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.14.1" - }, - "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/redoc-cli/node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/redoc-cli/node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "node_modules/redoc-cli/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": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/redoc-cli/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, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/redoc-cli/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "node_modules/redoc-cli/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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/redoc-cli/node_modules/uglify-js": { - "version": "3.13.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.9.tgz", - "integrity": "sha512-wZbyTQ1w6Y7fHdt8sJnHfSIuWeDgk6B5rCb4E/AM6QNNPbOMIZph21PW5dRB3h7Df0GszN+t7RuUH6sWK5bF0g==", - "dev": true, - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/redoc-cli/node_modules/update-browserslist-db": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz", - "integrity": "sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "peer": true, - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/redoc-cli/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, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/redoc-cli/node_modules/uri-js/node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/redoc-cli/node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/redoc-cli/node_modules/url-template": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", - "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/redoc-cli/node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "peer": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/redoc-cli/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - }, - "node_modules/redoc-cli/node_modules/webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "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.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "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/redoc-cli/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, - "peer": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/redoc-cli/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/redoc-cli/node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redoc-cli/node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "node_modules/redoc-cli/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/redoc-cli/node_modules/wrap-ansi/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/redoc-cli/node_modules/wrap-ansi/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/redoc-cli/node_modules/wrap-ansi/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/redoc-cli/node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/redoc-cli/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/redoc-cli/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/redoc-cli/node_modules/yaml-ast-parser": { - "version": "0.0.43", - "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", - "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", - "dev": true - }, - "node_modules/redoc-cli/node_modules/yargs": { - "version": "17.3.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", - "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "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.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/redoc-cli/node_modules/yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/reflect-metadata": { "version": "0.1.13", "license": "Apache-2.0", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 938aa877..f0416941 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "nest build", "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", - "generate-swagger-ui": "redoc-cli bundle swagger.yaml --output=./docs/index.html", + "generate-swagger-ui": "npx --yes @redocly/cli build-docs swagger.yaml --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "start:api": "nest start api", "start:api:prod": "node dist/apps/api/main.js", @@ -106,7 +106,6 @@ "license-report": "^6.4.0", "nock": "^13.3.8", "prettier": "^3.0.2", - "redoc-cli": "^0.13.21", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "trace-unhandled": "^2.0.1", @@ -118,31 +117,18 @@ "typescript": "^5.1.3" }, "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], + "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", - "setupFiles": [ - "dotenv/config" - ], + "setupFiles": ["dotenv/config"], "testRegex": ".*\\.spec\\.ts$", - "testPathIgnorePatterns": [ - ".*\\.mock\\.spec\\.ts$" - ], + "testPathIgnorePatterns": [".*\\.mock\\.spec\\.ts$"], "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], + "collectCoverageFrom": ["**/*.(t|j)s"], "coverageDirectory": "./coverage", "testEnvironment": "node", - "roots": [ - "/apps/", - "/libs/" - ], + "roots": ["/apps/", "/libs/"], "moduleNameMapper": { "^@content-watcher-common(|/.*)$": "/libs/common/src/$1" } From d6470a077905f4ea5f75b7b25b92c9235e53bb0d Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Wed, 24 Apr 2024 14:51:48 -0400 Subject: [PATCH 120/137] fix: standardize Docker containers (#35) * Update the building of Docker containers per our standards [here](https://docs.google.com/document/d/1Iv5RDx6-OV4S0vnn8DG4b6-zia0qkQpXa0Y9m7ihG5Y/edit#heading=h.s8rj6z75559l) * Update launching of services for local testing in `docker-compose.dev.yaml` * Update launching instructions in README Closes #31 --- .../{.env.cp.docker.dev => .env.docker.dev} | 13 ++- .../.github/workflows/release.yml | 1 - services/content-watcher/Dockerfile | 23 ++-- services/content-watcher/README.md | 55 +++++---- services/content-watcher/dev.Dockerfile | 7 +- .../content-watcher/docker-compose.dev.yaml | 104 ------------------ services/content-watcher/docker-compose.yaml | 103 +++++++++++++++++ services/content-watcher/package.json | 40 ++++--- .../content-watcher/scripts/local-setup.sh | 44 ++------ 9 files changed, 194 insertions(+), 196 deletions(-) rename services/content-watcher/{.env.cp.docker.dev => .env.docker.dev} (71%) delete mode 100644 services/content-watcher/docker-compose.dev.yaml create mode 100644 services/content-watcher/docker-compose.yaml diff --git a/services/content-watcher/.env.cp.docker.dev b/services/content-watcher/.env.docker.dev similarity index 71% rename from services/content-watcher/.env.cp.docker.dev rename to services/content-watcher/.env.docker.dev index 1b382ff8..cfdeaa37 100644 --- a/services/content-watcher/.env.cp.docker.dev +++ b/services/content-watcher/.env.docker.dev @@ -1,8 +1,9 @@ -IPFS_ENDPOINT="http://kubo_ipfs:5001" -IPFS_BASIC_AUTH_USER="" -IPFS_BASIC_AUTH_SECRET="" -IPFS_GATEWAY_URL="http://kubo_ipfs:8080/ipfs/[CID]" +IPFS_ENDPOINT=http://kubo_ipfs:5001 +IPFS_BASIC_AUTH_USER= +IPFS_BASIC_AUTH_SECRET= +IPFS_GATEWAY_URL=http://kubo_ipfs:8080/ipfs/[CID] +CHAIN_ENVIRONMENT=dev FREQUENCY_URL=ws://frequency:9944 PROVIDER_ID=1 REDIS_URL=redis://redis:6379 @@ -12,8 +13,8 @@ PROVIDER_ACCOUNT_SEED_PHRASE="//Alice" WEBHOOK_FAILURE_THRESHOLD=3 WEBHOOK_RETRY_INTERVAL_SECONDS=10 CAPACITY_LIMIT='{"type":"percentage", "value":80}' -ENVIRONMENT="dev" -API_PORT=3001 +ENVIRONMENT=dev +API_PORT=3000 FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 ASSET_EXPIRATION_INTERVAL_SECONDS=300 diff --git a/services/content-watcher/.github/workflows/release.yml b/services/content-watcher/.github/workflows/release.yml index 9d95aa4f..a56a734d 100644 --- a/services/content-watcher/.github/workflows/release.yml +++ b/services/content-watcher/.github/workflows/release.yml @@ -51,7 +51,6 @@ jobs: with: flavor: | latest=auto - prefix=api-,onlatest=true images: | ${{env.DOCKER_HUB_PROFILE}}/${{env.IMAGE_NAME}} tags: | diff --git a/services/content-watcher/Dockerfile b/services/content-watcher/Dockerfile index 131a8896..d519bb34 100644 --- a/services/content-watcher/Dockerfile +++ b/services/content-watcher/Dockerfile @@ -1,11 +1,11 @@ # Use a multi-stage build for efficiency -FROM node:18 AS builder +FROM node:20 AS builder -WORKDIR /usr/src/app +WORKDIR /app COPY package*.json ./ -RUN npm install +RUN npm ci COPY . . @@ -13,14 +13,21 @@ COPY . . RUN npm run build # Production stage -FROM node:18 +FROM node:20 -WORKDIR /usr/src/app +WORKDIR /app -COPY --from=builder /usr/src/app/dist ./dist +COPY --from=builder /app/dist ./dist COPY package*.json ./ -RUN npm install --only=production +RUN npm ci --omit=dev + +# We want jq and curl in the final image, but we don't need the support files +RUN apt-get update && \ + apt-get install -y jq curl tini && \ + apt-get clean && \ + rm -rf /usr/share/doc /usr/share/man /usr/share/zsh + EXPOSE 3000 -CMD ["sh", "-c", "npm run start:api:prod"] +ENTRYPOINT ["/usr/bin/tini", "--", "npm", "run", "start:prod"] diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index 4d943c29..9242a137 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -21,46 +21,55 @@ Before you begin, ensure you have met the following requirements: Follow these steps to set up and run Content Watcher: -### Clone the Repository +### 1. Clone the Repository +Clone the Content Watcher repository to your local machine: +```bash +git clone https://github.com/amplicalabls/content-watcher-service.git +``` -1. Clone the Content Watcher repository to your local machine: +### 2. Configure the app +The application is configurable by means of the environment. Default environment files are provided with sane values. The available environment configuration variables are document [here](./ENVIRONMENT.md), and a sample file is located [here](./env.template) - ```bash - git clone https://github.com/amplicalabls/content-watcher-service.git - ``` +Since running a full stack supporting the content-watcher service necessitates having an instance of the content-publishing services, those services are included in the docker-compose profile. The content-publishing services are separately configured using [.env.cp.docker.dev](./.env.cp.docker.dev). Documentation for that configuration can be found [here](https://github.com/AmplicaLabs/content-publishing-service/blob/main/ENVIRONMENT.md) -### Configure the app -The application is configurable by means of the environment. Default environment files are provided with sane values. The available environment configuration variables are document [here](./ENVIRONMENT.md), and a sample file is located [here](./env.template) +### 3. Start the service: +Run the following command to start the service: +```bash +docker-compose up -d +``` +## Testing ### Run a Full End-to-End Test - 1. Execute the following `make` command to deploy the entire stack: + ```bash + make test-start-services + ``` - ```bash - make test-start-services - ``` - - This command will set up the following services: - + This command will set up the following services: - **Frequency:** A local instance of Frequency will be set up with the default instant sealing mode. - **Redis:** A local instance of Redis will be initiated and configured for use by content publishing and content watcher services. - **Kubo IPFS:** A local instance of IPFS will be initiated and configured for use in content publishing and retrieval. - **Content Publishing API:** A local instance of the content publishing API will be utilized to publish content to IPFS and Frequency for content watcher tests. - **Content Publishing Worker:** A local instance of the content publishing worker will be employed to publish content to IPFS and Frequency for content watcher tests via dedicated processors. - The following setup scenarios will be executed during the stack initialization: + The following setup scenarios will be executed during the stack initialization: - - **Chain Setup Scenario:** A provider with MSA=1 will be created, with some user accounts, along with delegation to the provider. Capacity will be staked to MSA=1 to enable the provider to publish content on behalf of users. - - **DSNP Schemas:** DSNP schemas will be registered on Frequency. - - **Publish Some Example Content:** Example content will be published to IPFS and Frequency. Check the progress of content publishing at [Content Publishing BullBoard](http://0.0.0.0:3001/queues). + - **Chain Setup Scenario:** A provider with MSA=1 will be created, with some user accounts, along with delegation to the provider. Capacity will be staked to MSA=1 to enable the provider to publish content on behalf of users. + - **DSNP Schemas:** DSNP schemas will be registered on Frequency. + - **Publish Some Example Content:** Example content will be published to IPFS and Frequency. Check the progress of content publishing at [Content Publishing BullBoard](http://0.0.0.0:3001/queues). 2. Run the following `make` command to execute the content watcher tests: - ```bash - make test-e2e - ``` + make test-e2e + ``` 3. Alternatively, create a `.env` file, run `nest start api` to start the content watcher as a standalone service, register a webhook with the content watcher using [swagger](http://0.0.0.0:3000/api/docs/swagger#), and try the following scenarios: - - **Reset Scanner:** This action will reset the scanner to start from the beginning of the chain or whichever block is chosen to start with. Upon successful parsing, a respective announcement will be made to the webhook. - - **Put a Search Request:** This action will put a search request on the queue. The request requires a start block and end block. Upon successful parsing, a respective announcement will be made to the webhook. +- **Reset Scanner:** This action will reset the scanner to start from the beginning of the chain or whichever block is chosen to start with. Upon successful parsing, a respective announcement will be made to the webhook. +- **Put a Search Request:** This action will put a search request on the queue. The request requires a start block and end block. Upon successful parsing, a respective announcement will be made to the webhook. + +## Swagger UI +Check out the Swagger UI hosted on the app instance at [\/api/docs/swagger](http://localhost:3000/api/docs/swagger) to view the API documentation and submit requests to the service. + +## Queue Management +You may also view and manage the application's queue at [\/queues](http://localhost:3000/queues). diff --git a/services/content-watcher/dev.Dockerfile b/services/content-watcher/dev.Dockerfile index 99fcb102..9de164f4 100644 --- a/services/content-watcher/dev.Dockerfile +++ b/services/content-watcher/dev.Dockerfile @@ -1,10 +1,7 @@ -FROM node:18 +FROM node:20 WORKDIR /app -COPY . . -RUN npm install EXPOSE 3000 -ENV START_PROCESS="api" -CMD ["sh", "-c", "if [ \"$START_PROCESS\" = \"api\" ]; then npm run start:api; else npm run start:worker; fi"] +ENTRYPOINT [ "npm", "run", "start:watch" ] diff --git a/services/content-watcher/docker-compose.dev.yaml b/services/content-watcher/docker-compose.dev.yaml deleted file mode 100644 index 7e3da724..00000000 --- a/services/content-watcher/docker-compose.dev.yaml +++ /dev/null @@ -1,104 +0,0 @@ -version: '3' - -services: - redis: - image: redis:latest - ports: - - 6379:6379 - volumes: - - redis_data:/data/redis - networks: - - content-watcher-service - - frequency: - image: frequencychain/instant-seal-node:latest - ports: - - 9944:9944 - profiles: ['', 'instant'] - networks: - - content-watcher-service - container_name: frequency - volumes: - - chainstorage:/data - - frequency-interval: - image: frequencychain/instant-seal-node:latest - command: --sealing=interval --sealing-interval 3 --sealing-create-empty-blocks - ports: - - 9944:9944 - profiles: - - 'interval' - networks: - - content-watcher-service - container_name: frequency-interval-node - volumes: - - chainstorage:/data - - frequency-manual: - image: frequencychain/instant-seal-node:latest - command: --sealing=manual - ports: - - 9944:9944 - profiles: - - 'manual' - networks: - - content-watcher-service - container_name: frequency-manual-node - volumes: - - chainstorage:/data - - kubo_ipfs: - image: ipfs/kubo:latest - ports: - - 4001:4001 - - 5001:5001 - - 8080:8080 - networks: - - content-watcher-service - volumes: - - ipfs_data:/data/ipfs - - content-publishing-service-api: - image: amplicalabs/content-publishing-service:api-0.0.2-rc4 - ports: - - 3001:3001 - env_file: - - .env.cp.docker.dev - environment: - - START_PROCESS=api - - REDIS_URL=redis://redis:6379 - volumes: - - ./:/app - depends_on: - - redis - - frequency - - kubo_ipfs - networks: - - content-watcher-service - - content-publishing-service-worker: - image: amplicalabs/content-publishing-service:api-0.0.2-rc4 - env_file: - - .env.cp.docker.dev - environment: - - START_PROCESS=worker - - REDIS_URL=redis://redis:6379 - volumes: - - ./:/app - depends_on: - - redis - - frequency - - kubo_ipfs - networks: - - content-watcher-service - -volumes: - redis_data: - ipfs_data: - chainstorage: - external: false - -networks: - content-watcher-service: - - diff --git a/services/content-watcher/docker-compose.yaml b/services/content-watcher/docker-compose.yaml new file mode 100644 index 00000000..00a72725 --- /dev/null +++ b/services/content-watcher/docker-compose.yaml @@ -0,0 +1,103 @@ +services: + redis: + image: redis:latest + ports: + - 6379:6379 + volumes: + - redis_data:/data/redis + networks: + - content-watcher-service + + frequency: + image: dsnp/instant-seal-node-with-deployed-schemas:latest + # We need to specify the platform because it's the only image + # built by Frequency at the moment, and auto-pull won't work otherwise + platform: linux/amd64 + # Uncomment SEALING_MODE and SEALING_INTERVAL if you want to use interval sealing. + # Other options you may want to add depending on your test scenario. + # environment: + # - SEALING_MODE=interval + # - SEALING_INTERVAL=3 + # - CREATE_EMPTY_BLOCKS=true + # Uncomment below if you want to let the chain run and keep all of the historical blocks + # command: --state-pruning=archive + ports: + - 9944:9944 + networks: + - content-watcher-service + container_name: frequency + volumes: + - chainstorage:/data + + kubo_ipfs: + image: ipfs/kubo:latest + ports: + - 4001:4001 + - 5001:5001 + - 8080:8080 + networks: + - content-watcher-service + volumes: + - ipfs_data:/data/ipfs + + content-publishing-service-api: + image: amplicalabs/content-publishing-service:latest + # For now, this is the only platform image published. + platform: linux/amd64 + ports: + - 3001:3000 + env_file: + - .env.docker.dev + environment: + - START_PROCESS=api + depends_on: + - redis + - frequency + - kubo_ipfs + networks: + - content-watcher-service + + content-publishing-service-worker: + image: amplicalabs/content-publishing-service:latest + # For now, this is the only platform image published. + platform: linux/amd64 + env_file: + - .env.docker.dev + environment: + - START_PROCESS=worker + depends_on: + - redis + - frequency + - kubo_ipfs + networks: + - content-watcher-service + + content-watcher-service: + pull_policy: never + image: content-watcher-service + build: + context: . + dockerfile: dev.Dockerfile + tags: + - content-watcher-service:latest + env_file: + - .env.docker.dev + volumes: + - ./:/app + depends_on: + - redis + - frequency + - kubo_ipfs + - content-publishing-service-api + - content-publishing-service-worker + networks: + - content-watcher-service + +volumes: + redis_data: + ipfs_data: + chainstorage: + external: false + +networks: + content-watcher-service: diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index f0416941..87533e97 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -1,6 +1,6 @@ { "name": "content-watcher-service", - "version": "0.1.0", + "version": "0.9.0", "description": "Services to publish content on DSNP/Frequency", "main": "dist/apps/api/main.js", "scripts": { @@ -8,15 +8,16 @@ "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", "generate-swagger-ui": "npx --yes @redocly/cli build-docs swagger.yaml --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", - "start:api": "nest start api", - "start:api:prod": "node dist/apps/api/main.js", - "start:api:dev": "set -a ; . .env ; nest start api", - "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", + "start": "nest start api", + "start:watch": "nest start api --watch", + "start:prod": "node dist/apps/api/main.js", + "start:dev": "set -a ; . .env ; nest start api", + "start:debug": "set -a ; . .env ; nest start api --debug --watch", "docker-build": "docker build -t content-watcher-service .", - "docker-build:dev": "docker-compose -f docker-compose.dev.yaml build", + "docker-build:dev": "docker-compose build", "docker-run": " build -t content-watcher-service-deploy . ; docker run -p 6379:6379 --env-file .env content-watcher-service-deploy", - "docker-run:dev": "docker-compose -f docker-compose.dev.yaml up -d ; docker-compose -f docker-compose.dev.yaml logs", - "docker-stop:dev": "docker-compose -f docker-compose.dev.yaml stop", + "docker-run:dev": "docker-compose up -d ; docker-compose logs", + "docker-stop:dev": "docker-compose stop", "clean": "rm -Rf dist", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "pretest": "cp env.template .env", @@ -117,18 +118,31 @@ "typescript": "^5.1.3" }, "jest": { - "moduleFileExtensions": ["js", "json", "ts"], + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], "rootDir": ".", - "setupFiles": ["dotenv/config"], + "setupFiles": [ + "dotenv/config" + ], "testRegex": ".*\\.spec\\.ts$", - "testPathIgnorePatterns": [".*\\.mock\\.spec\\.ts$"], + "testPathIgnorePatterns": [ + ".*\\.mock\\.spec\\.ts$" + ], "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "collectCoverageFrom": ["**/*.(t|j)s"], + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], "coverageDirectory": "./coverage", "testEnvironment": "node", - "roots": ["/apps/", "/libs/"], + "roots": [ + "/apps/", + "/libs/" + ], "moduleNameMapper": { "^@content-watcher-common(|/.*)$": "/libs/common/src/$1" } diff --git a/services/content-watcher/scripts/local-setup.sh b/services/content-watcher/scripts/local-setup.sh index e0929b0b..265432dd 100755 --- a/services/content-watcher/scripts/local-setup.sh +++ b/services/content-watcher/scripts/local-setup.sh @@ -1,30 +1,24 @@ #!/bin/bash -PROFILE=instant PROJECT_NAME=cr MODE=startup function help() { cat << EOI -Usage: $( basename ${1} ) -i|-d [-s scenario] [-p profile-name] [-n project-name] [-h] +Usage: $( basename ${1} ) -i|-d [-s scenario] [-n project-name] [-h] Where: -i initialize services -d delete services - -p profile-name 'profile-name' is the name of a Docker profile from - docker-compose.dev.yaml. Options are: - 'instant' - Use an instant-seal Frequency node (default) - 'interval' - Use an interval-seal Frequency node - -n project-name 'project-name' is the prefix that will be added to container, volume, and network names in Docker. (default: 'cw') EOI } -while getopts "hp:n:ids:" OPTION +while getopts "hn:ids:" OPTION do case ${OPTION} in @@ -35,14 +29,6 @@ do "n") PROJECT_NAME="${OPTARG}" ;; - "p") if [ "${OPTARG}" = "''" ] - then - PROFILE= - else - PROFILE="${OPTARG}" - fi - ;; - "i") MODE=startup ;; @@ -56,25 +42,11 @@ do esac done -if [ -n "${PROFILE}" ] -then - if [[ ${PROFILE} = "interval" || ${PROFILE} = "instant" ]] - then - PROFILE="--profile ${PROFILE}" - else - echo "Invalid profile specified: ${PROFILE}" - help $0 - exit 1 - fi -else - PROFILE="--profile interval" -fi - export TOPDIR=$( dirname ${0} )/.. function teardown() { # Stop previously running containers - docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} down + docker compose --project-name ${PROJECT_NAME} down # Remove chain, ipfs & redis volumes docker volume rm ${PROJECT_NAME}_chainstorage 2>/dev/null @@ -88,20 +60,20 @@ function teardown() { function startup() { # Start containers for chain, ipfs & redis ## start frequency service first as we want to set the chain state - docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} up -d frequency - + docker compose --project-name ${PROJECT_NAME} up -d frequency + # Set up chain scenario, i.e. set provider, delegation and schemas ( cd ${TOPDIR} && npm i && npm run local:init ) # start rest of services - docker compose --project-name ${PROJECT_NAME} -f ${TOPDIR}/docker-compose.dev.yaml ${PROFILE} up -d + docker compose --project-name ${PROJECT_NAME} up -d # sleep for 5 seconds to wait for chain to start and service to be registered sleep 5 - + # publish some content ( cd ${TOPDIR}/scripts/content-setup && npm i && npm run main) - + # Make sure pm2 is installed if ! which pm2 >| /dev/null then From fa58dab5ee3cd5250ef39a3e4e6ed8439d5e75c1 Mon Sep 17 00:00:00 2001 From: Puneet Saraswat <61435908+saraswatpuneet@users.noreply.github.com> Date: Mon, 13 May 2024 10:38:50 -0500 Subject: [PATCH 121/137] updgrade and cleanup/lint fixes (#51) ## Details - Upgrade depdencies minus polkadotjs and related - Remove as much unused packages - Update lint and address errors --- services/content-watcher/.eslintrc.json | 64 - .../apps/api/src/api.module.ts | 4 +- .../apps/api/src/api.service.ts | 8 +- services/content-watcher/apps/api/src/main.ts | 4 +- .../content-watcher/apps/api/src/metadata.ts | 98 +- .../apps/api/test/app.e2e-spec.ts | 3 - services/content-watcher/eslint.config.mjs | 27 + .../src/blockchain/blockchain-constants.ts | 41 - .../src/blockchain/blockchain.service.ts | 13 +- .../libs/common/src/blockchain/create-keys.ts | 1 - .../libs/common/src/blockchain/event-error.ts | 4 +- .../libs/common/src/blockchain/extrinsic.ts | 2 +- .../common/src/config/config.service.spec.ts | 9 +- .../libs/common/src/constants.ts | 6 +- .../libs/common/src/crawler/crawler.module.ts | 2 +- .../libs/common/src/crawler/crawler.ts | 4 +- .../libs/common/src/dtos/activity.dto.ts | 8 +- .../libs/common/src/dtos/common.dto.ts | 2 +- .../libs/common/src/ipfs/ipfs.dsnp.ts | 3 +- .../libs/common/src/ipfs/ipfs.module.ts | 4 +- .../common/src/pubsub/announcers/broadcast.ts | 2 +- .../common/src/pubsub/announcers/profile.ts | 2 +- .../common/src/pubsub/announcers/reaction.ts | 2 +- .../common/src/pubsub/announcers/reply.ts | 2 +- .../common/src/pubsub/announcers/tombstone.ts | 2 +- .../common/src/pubsub/announcers/update.ts | 2 +- .../libs/common/src/pubsub/pubsub.module.ts | 7 +- .../libs/common/src/pubsub/pubsub.service.ts | 2 +- .../libs/common/src/scanner/scanner.module.ts | 2 +- .../libs/common/src/scanner/scanner.ts | 6 +- .../libs/common/src/utils/base-consumer.ts | 2 +- .../libs/common/src/utils/ipfs.client.ts | 4 +- .../libs/common/src/utils/processing.ts | 12 +- .../libs/common/src/utils/queues.ts | 84 +- .../libs/common/src/utils/redis.ts | 92 +- services/content-watcher/package-lock.json | 14391 ++++++++-------- services/content-watcher/package.json | 110 +- 37 files changed, 7801 insertions(+), 7230 deletions(-) delete mode 100644 services/content-watcher/.eslintrc.json create mode 100644 services/content-watcher/eslint.config.mjs delete mode 100644 services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts diff --git a/services/content-watcher/.eslintrc.json b/services/content-watcher/.eslintrc.json deleted file mode 100644 index 98fe9ce2..00000000 --- a/services/content-watcher/.eslintrc.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "env": { - "browser": true, - "es2021": true - }, - "extends": [ - "airbnb-base", - "prettier" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json", - "sourceType": "module" - }, - "settings": { - "import/extensions": [ - "error", - "ignorePackages", - { - "js": "never", - "jsx": "never", - "ts": "never", - "tsx": "never" - } - ], - "import/parsers": { - "@typescript-eslint/parser": [".ts", ".tsx"] - }, - "import/resolver": { - "typescript": { - "directory": "./tsconfig.json" - }, - "node": { - "extensions": [".js", ".jsx", ".ts", ".d.ts", ".tsx"] - } - }, - "react": { - "version": "999.99.99" - } - }, - "rules": { - "no-console": "off", - "import/extensions": [ - "error", - "ignorePackages", - { - "js": "never", - "jsx": "never", - "ts": "never", - "tsx": "never" - } - ], - "import/no-unresolved": [2, { "commonjs": true, "amd": true }], - "import/named": 2, - "import/namespace": 2, - "import/default": 2, - "import/export": 2, - "import/prefer-default-export": "off", - "indent": "off", - "no-unused-vars": "off", - "prettier/prettier": 2 - }, - "plugins": ["import", "prettier"] -} diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 46c2f092..aa6addc7 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -2,12 +2,12 @@ import { Module } from '@nestjs/common'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { RedisModule } from '@songkeys/nestjs-redis'; import { BullBoardModule } from '@bull-board/nestjs'; import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; import { ExpressAdapter } from '@bull-board/express'; import { ApiController } from './api.controller'; -import { QueueConstants } from '../../../libs/common/src'; +import * as QueueConstants from '../../../libs/common/src'; import { ApiService } from './api.service'; import { ConfigModule } from '../../../libs/common/src/config/config.module'; import { ConfigService } from '../../../libs/common/src/config/config.service'; diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index d923ae33..a9389da5 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -1,15 +1,15 @@ import { Injectable, Logger } from '@nestjs/common'; import { createHash } from 'crypto'; -import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { InjectRedis } from '@songkeys/nestjs-redis'; import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; -import { ContentSearchRequestDto, QueueConstants, calculateJobId } from '../../../libs/common/src'; +import { ContentSearchRequestDto, REQUEST_QUEUE_NAME, calculateJobId } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../../../libs/common/src/constants'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; import { WebhookRegistrationDto } from '../../../libs/common/src/dtos/subscription.webhook.dto'; -import { RedisUtils } from '../../../libs/common/src/utils/redis'; +import * as RedisUtils from '../../../libs/common/src/utils/redis'; @Injectable() export class ApiService { @@ -17,7 +17,7 @@ export class ApiService { constructor( @InjectRedis() private redis: Redis, - @InjectQueue(QueueConstants.REQUEST_QUEUE_NAME) private requestQueue: Queue, + @InjectQueue(REQUEST_QUEUE_NAME) private requestQueue: Queue, private readonly scannerService: ScannerService, ) { this.logger = new Logger(this.constructor.name); diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index 1929fa0c..c3dfd1d8 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -9,7 +9,9 @@ const logger = new Logger('main'); // Monkey-patch BigInt so that JSON.stringify will work // eslint-disable-next-line -BigInt.prototype['toJSON'] = function () { return this.toString() }; +BigInt.prototype['toJSON'] = function () { + return this.toString(); +}; async function bootstrap() { const app = await NestFactory.create(ApiModule, { diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index 8596c896..988f46b3 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -1,8 +1,94 @@ /* eslint-disable */ export default async () => { - const t = { - ["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"), - ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto") - }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {} } }]] } }; -}; \ No newline at end of file + const t = { + ['../../../libs/common/src/dtos/activity.dto']: await import('../../../libs/common/src/dtos/activity.dto'), + ['../../../libs/common/src/dtos/announcement.dto']: await import('../../../libs/common/src/dtos/announcement.dto'), + }; + return { + '@nestjs/swagger': { + models: [ + [ + import('../../../libs/common/src/dtos/common.dto'), + { + DsnpUserIdParam: { userDsnpId: { required: true, type: () => String } }, + AnnouncementResponseDto: { referenceId: { required: true, type: () => String } }, + UploadResponseDto: { assetIds: { required: true, type: () => [String] } }, + FilesUploadDto: { files: { required: true, type: () => [Object] } }, + }, + ], + [ + import('../../../libs/common/src/dtos/activity.dto'), + { + LocationDto: { + name: { required: true, type: () => String, minLength: 1 }, + accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, + altitude: { required: false, type: () => Number }, + latitude: { required: false, type: () => Number }, + longitude: { required: false, type: () => Number }, + radius: { required: false, type: () => Number, minimum: 0 }, + units: { required: false, enum: t['../../../libs/common/src/dtos/activity.dto'].UnitTypeDto }, + }, + AssetReferenceDto: { + referenceId: { required: true, type: () => String, minLength: 1 }, + height: { required: false, type: () => Number, minimum: 1 }, + width: { required: false, type: () => Number, minimum: 1 }, + duration: { required: false, type: () => String, pattern: 'DURATION_REGEX' }, + }, + TagDto: { + type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].TagTypeDto }, + name: { required: false, type: () => String, minLength: 1 }, + mentionedId: { required: false, type: () => String, minLength: 1, pattern: 'DSNP_USER_URI_REGEX' }, + }, + AssetDto: { + type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].AttachmentTypeDto }, + references: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, + name: { required: false, type: () => String, minLength: 1 }, + href: { required: false, type: () => String, minLength: 1 }, + }, + BaseActivityDto: { + name: { required: false, type: () => String }, + tag: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].TagDto] }, + location: { required: false, type: () => t['../../../libs/common/src/dtos/activity.dto'].LocationDto }, + }, + NoteActivityDto: { + content: { required: true, type: () => String, minLength: 1 }, + published: { required: true, type: () => String, pattern: 'ISO8601_REGEX' }, + assets: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetDto] }, + }, + ProfileActivityDto: { + icon: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, + summary: { required: false, type: () => String }, + published: { required: false, type: () => String, pattern: 'ISO8601_REGEX' }, + }, + }, + ], + [ + import('../../../libs/common/src/dtos/announcement.dto'), + { + BroadcastDto: { content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto } }, + ReplyDto: { + inReplyTo: { required: true, type: () => String, pattern: 'DSNP_CONTENT_URI_REGEX' }, + content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, + }, + TombstoneDto: { + targetContentHash: { required: true, type: () => String, pattern: 'DSNP_CONTENT_HASH_REGEX' }, + targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, + }, + UpdateDto: { + targetContentHash: { required: true, type: () => String, pattern: 'DSNP_CONTENT_HASH_REGEX' }, + targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, + content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, + }, + ReactionDto: { + emoji: { required: true, type: () => String, minLength: 1, pattern: 'DSNP_EMOJI_REGEX' }, + apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, + inReplyTo: { required: true, type: () => String, pattern: 'DSNP_CONTENT_URI_REGEX' }, + }, + ProfileDto: { profile: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].ProfileActivityDto } }, + }, + ], + ], + controllers: [[import('./api.controller'), { ApiController: { health: {} } }]], + }, + }; +}; diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index d52ac212..45b983ca 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -1,10 +1,7 @@ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable no-undef */ import { INestApplication, ValidationPipe } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import request from 'supertest'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import nock from 'nock'; import { ApiModule } from '../src/api.module'; import { BlockchainService } from '../../../libs/common/src/blockchain/blockchain.service'; import { ResetScannerDto } from '../../../libs/common/src'; diff --git a/services/content-watcher/eslint.config.mjs b/services/content-watcher/eslint.config.mjs new file mode 100644 index 00000000..4d24646e --- /dev/null +++ b/services/content-watcher/eslint.config.mjs @@ -0,0 +1,27 @@ +// @ts-check + +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import jestlint from 'eslint-plugin-jest'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.strict, + ...tseslint.configs.stylistic, + { + files: ['**/*.spec.ts'], + ...jestlint.configs['flat/recommended'], + }, + { + rules: { + indent: 'off', + 'no-unused-vars': 'off', + '@typescript-eslint/no-non-null-assertion': 'warn', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-extraneous-class': 'off', + '@typescript-eslint/consistent-type-definitions': 'off', + }, + }, +); \ No newline at end of file diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts b/services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts deleted file mode 100644 index 7172599b..00000000 --- a/services/content-watcher/libs/common/src/blockchain/blockchain-constants.ts +++ /dev/null @@ -1,41 +0,0 @@ -export namespace BlockchainConstants { - interface IExtrinsicCall { - pallet: string; - extrinsic: string; - } - - interface IChainEvent { - eventPallet: string; - event: string; - } - - interface IChainQuery { - queryPallet: string; - query: string; - } - - interface IChainRpc { - rpcPallet: string; - rpc: string; - } - const PALLET_FREQ_TX_PYMT = 'frequencyTxPayment'; - const PALLET_STATEFUL_STORAGE = 'statefulStorage'; - - const EX_PAY_CAPACITY_BATCH = 'payWithCapacityBatchAll'; - const EX_UPSERT_PAGE = 'upsertPage'; - - const PAY_WITH_CAPACITY_BATCH: IExtrinsicCall = { pallet: PALLET_FREQ_TX_PYMT, extrinsic: EX_PAY_CAPACITY_BATCH }; - - /** - * The number of blocks to crawl for a given job - * @type {number} - * @memberof BlockchainConstants - * @static - * @readonly - * @public - * @constant - * @description - * The number of blocks to crawl for a given job - */ - export const NUMBER_BLOCKS_TO_CRAWL = 32n; // TODO: take from tx, keeping it constant to default tx mortality -} diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index 43f30308..c75b4cf1 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -1,14 +1,12 @@ /* eslint-disable no-underscore-dangle */ import { Injectable, Logger, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common'; import { ApiPromise, ApiRx, HttpProvider, WsProvider } from '@polkadot/api'; -import { firstValueFrom, from } from 'rxjs'; +import { firstValueFrom } from 'rxjs'; import { options } from '@frequency-chain/api-augment'; import { KeyringPair } from '@polkadot/keyring/types'; -import { BlockHash, BlockNumber, DispatchError, DispatchInfo, Hash, SignedBlock } from '@polkadot/types/interfaces'; +import { BlockHash, BlockNumber, SignedBlock } from '@polkadot/types/interfaces'; import { SubmittableExtrinsic } from '@polkadot/api/types'; -import { AnyNumber, ISubmittableResult, RegistryError } from '@polkadot/types/types'; -import { u32, Option, u128, u16 } from '@polkadot/types'; -import { PalletCapacityCapacityDetails, PalletCapacityEpochInfo, PalletSchemasSchema } from '@polkadot/types/lookup'; +import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; import { ConfigService } from '../config/config.service'; import { Extrinsic } from './extrinsic'; @@ -121,9 +119,4 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS public async getNonce(account: Uint8Array): Promise { return this.rpc('system', 'accountNextIndex', account); } - - public async getSchema(schemaId: number): Promise { - const schema: PalletSchemasSchema = await this.query('schemas', 'schemas', schemaId); - return schema; - } } diff --git a/services/content-watcher/libs/common/src/blockchain/create-keys.ts b/services/content-watcher/libs/common/src/blockchain/create-keys.ts index b6fca978..7d1bb0b8 100644 --- a/services/content-watcher/libs/common/src/blockchain/create-keys.ts +++ b/services/content-watcher/libs/common/src/blockchain/create-keys.ts @@ -1,7 +1,6 @@ import { Keyring } from '@polkadot/api'; import { KeyringPair } from '@polkadot/keyring/types'; -// eslint-disable-next-line import/no-mutable-exports export let keyring: Keyring; export function createKeys(uri: string): KeyringPair { diff --git a/services/content-watcher/libs/common/src/blockchain/event-error.ts b/services/content-watcher/libs/common/src/blockchain/event-error.ts index fdaedcd2..831cc210 100644 --- a/services/content-watcher/libs/common/src/blockchain/event-error.ts +++ b/services/content-watcher/libs/common/src/blockchain/event-error.ts @@ -2,9 +2,9 @@ import { DispatchError } from '@polkadot/types/interfaces'; import { SpRuntimeDispatchError } from '@polkadot/types/lookup'; export class EventError extends Error { - name: string = ''; + name = ''; - message: string = ''; + message = ''; stack?: string = ''; diff --git a/services/content-watcher/libs/common/src/blockchain/extrinsic.ts b/services/content-watcher/libs/common/src/blockchain/extrinsic.ts index d40cdc13..cf4a77d1 100644 --- a/services/content-watcher/libs/common/src/blockchain/extrinsic.ts +++ b/services/content-watcher/libs/common/src/blockchain/extrinsic.ts @@ -33,7 +33,7 @@ import { filter, firstValueFrom, map, pipe, tap } from 'rxjs'; import { KeyringPair } from '@polkadot/keyring/types'; import { EventError } from './event-error'; -export type EventMap = { [key: string]: Event }; +export type EventMap = Record; function eventKey(event: Event): string { return `${event.section}.${event.method}`; diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index 4cf39849..337d8a74 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -1,8 +1,3 @@ -/* eslint-disable import/no-extraneous-dependencies */ -/* -https://docs.nestjs.com/fundamentals/testing#unit-testing -*/ - import { Test } from '@nestjs/testing'; import { describe, it, expect, beforeAll, jest } from '@jest/globals'; import { ConfigModule } from '@nestjs/config'; @@ -11,7 +6,9 @@ import { configModuleOptions } from './env.config'; const setupConfigService = async (envObj: any): Promise => { jest.resetModules(); + Object.keys(process.env).forEach((key) => { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete process.env[key]; }); process.env = { @@ -35,7 +32,7 @@ const setupConfigService = async (envObj: any): Promise => { }; describe('ContentWatcherConfigService', () => { - const ALL_ENV: { [key: string]: string | undefined } = { + const ALL_ENV: Record = { REDIS_URL: undefined, FREQUENCY_URL: undefined, STARTING_BLOCK: undefined, diff --git a/services/content-watcher/libs/common/src/constants.ts b/services/content-watcher/libs/common/src/constants.ts index c9856cdd..62889b65 100644 --- a/services/content-watcher/libs/common/src/constants.ts +++ b/services/content-watcher/libs/common/src/constants.ts @@ -11,16 +11,16 @@ export const CAPACITY_EPOCH_TIMEOUT_NAME = 'capacity-epoch-timeout'; * Last seen block number key for Redis * @type {string} */ -export const LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY: string = 'lastSeenBlockNumberScanner'; +export const LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY = 'lastSeenBlockNumberScanner'; /** * Filters and Events to watch key for Redis * @type {string} */ -export const EVENTS_TO_WATCH_KEY: string = 'eventsToWatch'; +export const EVENTS_TO_WATCH_KEY = 'eventsToWatch'; /** * Registered Webhook key for Redis * @type {string} */ -export const REGISTERED_WEBHOOK_KEY: string = 'registeredWebhook'; +export const REGISTERED_WEBHOOK_KEY = 'registeredWebhook'; diff --git a/services/content-watcher/libs/common/src/crawler/crawler.module.ts b/services/content-watcher/libs/common/src/crawler/crawler.module.ts index cafeedea..8a681dcd 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.module.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.module.ts @@ -9,7 +9,7 @@ import { ConfigModule } from '../config/config.module'; import { CrawlerService } from './crawler'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { ConfigService } from '../config/config.service'; -import { QueueConstants } from '../utils/queues'; +import * as QueueConstants from '../utils/queues'; @Module({ imports: [ diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 0c51daf5..55405185 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectQueue, Processor } from '@nestjs/bullmq'; import Redis from 'ioredis'; -import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { InjectRedis } from '@songkeys/nestjs-redis'; import { Vec, u16, u32 } from '@polkadot/types'; import { BlockPaginationResponseMessage, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Job, Queue } from 'bullmq'; @@ -10,7 +10,7 @@ import { firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { BlockchainService } from '../blockchain/blockchain.service'; -import { QueueConstants } from '../utils/queues'; +import * as QueueConstants from '../utils/queues'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; diff --git a/services/content-watcher/libs/common/src/dtos/activity.dto.ts b/services/content-watcher/libs/common/src/dtos/activity.dto.ts index 2c079705..86ad407d 100644 --- a/services/content-watcher/libs/common/src/dtos/activity.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/activity.dto.ts @@ -130,7 +130,7 @@ export class AssetDto { @ArrayNotEmpty() @ArrayUnique((o) => o.referenceId) @Type(() => AssetReferenceDto) - references?: Array; + references?: AssetReferenceDto[]; @IsOptional() @IsString() @@ -152,7 +152,7 @@ export class BaseActivityDto { @ValidateNested({ each: true }) @IsArray() @Type(() => TagDto) - tag?: Array; + tag?: TagDto[]; @IsOptional() @ValidateNested() @@ -173,7 +173,7 @@ export class NoteActivityDto extends BaseActivityDto { @ValidateNested({ each: true }) @IsArray() @Type(() => AssetDto) - assets?: Array; + assets?: AssetDto[]; } export class ProfileActivityDto extends BaseActivityDto { @@ -182,7 +182,7 @@ export class ProfileActivityDto extends BaseActivityDto { @IsArray() @ArrayUnique((o) => o.referenceId) @Type(() => AssetReferenceDto) - icon?: Array; + icon?: AssetReferenceDto[]; @IsOptional() @IsString() diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index 74a1c391..de725582 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -16,7 +16,7 @@ export class AnnouncementResponseDto { } export class UploadResponseDto { - assetIds: Array; + assetIds: string[]; } export class FilesUploadDto { diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index d6210063..7b7c1b3b 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -5,7 +5,8 @@ import { hexToString } from '@polkadot/util'; import parquet from '@dsnp/parquetjs'; import { bases } from 'multiformats/basics'; import { ConfigService } from '../config/config.service'; -import { QueueConstants, calculateJobId } from '..'; +import { calculateJobId } from '..'; +import * as QueueConstants from '../utils/queues'; import { IIPFSJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts index 056beb69..72c2e62d 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts @@ -5,11 +5,11 @@ https://docs.nestjs.com/modules import { BullModule } from '@nestjs/bullmq'; import { Module } from '@nestjs/common'; import { EventEmitterModule } from '@nestjs/event-emitter'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; +import { RedisModule } from '@songkeys/nestjs-redis'; import { ConfigModule } from '../config/config.module'; import { ConfigService } from '../config/config.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; -import { QueueConstants } from '..'; +import * as QueueConstants from '../utils/queues'; import { IPFSContentProcessor } from './ipfs.dsnp'; import { IpfsService } from '../utils/ipfs.client'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts b/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts index 635c1bde..57027bc8 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import { QueueConstants } from '../..'; +import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts b/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts index ee15e27f..ebcb85de 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import { QueueConstants } from '../..'; +import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts b/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts index a9325fd9..5af38f2b 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import { QueueConstants } from '../..'; +import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts b/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts index e1f79b6b..a68bad97 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import { QueueConstants } from '../..'; +import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts b/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts index 678f1535..a179aad0 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import { QueueConstants } from '../..'; +import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/update.ts b/services/content-watcher/libs/common/src/pubsub/announcers/update.ts index 682d2ef9..6e22033b 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/update.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/update.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import { QueueConstants } from '../..'; +import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts index 731149b2..963141e7 100644 --- a/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts @@ -2,13 +2,10 @@ import { Module } from '@nestjs/common'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; -import { RedisModule } from '@liaoliaots/nestjs-redis'; -import { BullBoardModule } from '@bull-board/nestjs'; -import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; -import { ExpressAdapter } from '@bull-board/express'; +import { RedisModule } from '@songkeys/nestjs-redis'; import { ConfigModule } from '../config/config.module'; import { ConfigService } from '../config/config.service'; -import { QueueConstants } from '../utils/queues'; +import * as QueueConstants from '../utils/queues'; import { PubSubService } from './pubsub.service'; import { BroadcastSubscriber } from './announcers/broadcast'; import { ProfileSubscriber } from './announcers/profile'; diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts index 0d49313b..b04ce983 100644 --- a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts @@ -1,4 +1,4 @@ -import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { InjectRedis } from '@songkeys/nestjs-redis'; import { Injectable, Logger } from '@nestjs/common'; import Redis from 'ioredis'; import axios from 'axios'; diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts index a34f0e10..43b1c119 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.module.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -9,7 +9,7 @@ import { ConfigModule } from '../config/config.module'; import { ScannerService } from './scanner'; import { BlockchainModule } from '../blockchain/blockchain.module'; import { ConfigService } from '../config/config.service'; -import { QueueConstants } from '../utils/queues'; +import * as QueueConstants from '../utils/queues'; @Module({ imports: [ diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 95e06144..fff51ca0 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -2,7 +2,7 @@ import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import Redis from 'ioredis'; -import { InjectRedis } from '@liaoliaots/nestjs-redis'; +import { InjectRedis } from '@songkeys/nestjs-redis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; import { Vec } from '@polkadot/types'; @@ -12,11 +12,11 @@ import { BlockNumber } from '@polkadot/types/interfaces'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; -import { QueueConstants } from '../utils/queues'; +import * as QueueConstants from '../utils/queues'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; -import { RedisUtils } from '../utils/redis'; +import * as RedisUtils from '../utils/redis'; import { MessageResponseWithSchemaId } from '../interfaces/announcement_response'; @Injectable() diff --git a/services/content-watcher/libs/common/src/utils/base-consumer.ts b/services/content-watcher/libs/common/src/utils/base-consumer.ts index cc752f09..0f7173d5 100644 --- a/services/content-watcher/libs/common/src/utils/base-consumer.ts +++ b/services/content-watcher/libs/common/src/utils/base-consumer.ts @@ -1,7 +1,7 @@ import { OnWorkerEvent, WorkerHost } from '@nestjs/bullmq'; import { Logger, OnModuleDestroy } from '@nestjs/common'; import { Job, Worker } from 'bullmq'; -import { ProcessingUtils } from './processing'; +import * as ProcessingUtils from './processing'; export abstract class BaseConsumer extends WorkerHost implements OnModuleDestroy { protected logger: Logger; diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index 414d8f3e..c7547990 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -65,7 +65,7 @@ export class IpfsService { }; } - public async ipfsPin(mimeType: string, file: Buffer, calculateDsnpHash: boolean = true): Promise { + public async ipfsPin(mimeType: string, file: Buffer, calculateDsnpHash = true): Promise { const fileName = calculateDsnpHash ? await this.ipfsHashBuffer(file) : randomUUID().toString(); const extension = getExtension(mimeType); if (extension === false) { @@ -83,7 +83,7 @@ export class IpfsService { * @param checkExistence * @returns buffer of the data if exists and an empty buffer if not */ - public async getPinned(cid: string, checkExistence: boolean = true): Promise { + public async getPinned(cid: string, checkExistence = true): Promise { if (checkExistence && !(await this.isPinned(cid))) { return Promise.resolve(Buffer.alloc(0)); } diff --git a/services/content-watcher/libs/common/src/utils/processing.ts b/services/content-watcher/libs/common/src/utils/processing.ts index 060fa6b6..b29ad2d1 100644 --- a/services/content-watcher/libs/common/src/utils/processing.ts +++ b/services/content-watcher/libs/common/src/utils/processing.ts @@ -1,8 +1,6 @@ -export namespace ProcessingUtils { - export const MAX_WAIT_FOR_GRACE_FULL_SHUTDOWN_MS = 6 * 1000; - export const DELAY_TO_CHECK_FOR_SHUTDOWN_MS = 300; - export async function delay(ms): Promise { - // eslint-disable-next-line no-promise-executor-return - return new Promise((resolve) => setTimeout(resolve, ms)); - } +export const MAX_WAIT_FOR_GRACE_FULL_SHUTDOWN_MS = 6 * 1000; +export const DELAY_TO_CHECK_FOR_SHUTDOWN_MS = 300; +export async function delay(ms): Promise { + // eslint-disable-next-line no-promise-executor-return + return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/utils/queues.ts index 77019b3a..42dcaa75 100644 --- a/services/content-watcher/libs/common/src/utils/queues.ts +++ b/services/content-watcher/libs/common/src/utils/queues.ts @@ -1,50 +1,46 @@ import { createHash } from 'crypto'; import { AnnouncementTypeDto } from '../dtos/common.dto'; -export namespace QueueConstants { - /** - * Name of the queue that has all incoming IPFS messages from the blockchain - */ - export const IPFS_QUEUE = 'contentIpfsQueue'; - - /** - * Name of the queue that has all incoming requests for specific announcements - * from the blockchain - */ - export const REQUEST_QUEUE_NAME = 'contentRequestQueue'; - - /** - * Name of the queue that has all outgoing announcements from the blockchain - */ - export const BROADCAST_QUEUE_NAME = 'watchBroadcastQueue'; - export const REPLY_QUEUE_NAME = 'watchReplyQueue'; - export const REACTION_QUEUE_NAME = 'watchReactionQueue'; - export const TOMBSTONE_QUEUE_NAME = 'watchTombstoneQueue'; - export const PROFILE_QUEUE_NAME = 'watchProfileQueue'; - export const UPDATE_QUEUE_NAME = 'watchUpdateQueue'; - /** - * Map between announcement type and it's queueName - */ - export const ANNOUNCEMENT_TO_QUEUE_NAME_MAP = new Map([ - [AnnouncementTypeDto.BROADCAST, BROADCAST_QUEUE_NAME], - [AnnouncementTypeDto.REPLY, REPLY_QUEUE_NAME], - [AnnouncementTypeDto.REACTION, REACTION_QUEUE_NAME], - [AnnouncementTypeDto.TOMBSTONE, TOMBSTONE_QUEUE_NAME], - [AnnouncementTypeDto.PROFILE, PROFILE_QUEUE_NAME], - [AnnouncementTypeDto.UPDATE, UPDATE_QUEUE_NAME], - ]); - /** - * Map between queue name and it's announcement type - */ - export const QUEUE_NAME_TO_ANNOUNCEMENT_MAP = new Map([ - [BROADCAST_QUEUE_NAME, AnnouncementTypeDto.BROADCAST], - [REPLY_QUEUE_NAME, AnnouncementTypeDto.REPLY], - [REACTION_QUEUE_NAME, AnnouncementTypeDto.REACTION], - [TOMBSTONE_QUEUE_NAME, AnnouncementTypeDto.TOMBSTONE], - [PROFILE_QUEUE_NAME, AnnouncementTypeDto.PROFILE], - [UPDATE_QUEUE_NAME, AnnouncementTypeDto.UPDATE], - ]); -} +/** + * Name of the queue that has all incoming IPFS messages from the blockchain + */ +export const IPFS_QUEUE = 'contentIpfsQueue'; +/** + * Name of the queue that has all incoming requests for specific announcements + * from the blockchain + */ +export const REQUEST_QUEUE_NAME = 'contentRequestQueue'; +/** + * Name of the queue that has all outgoing announcements from the blockchain + */ +export const BROADCAST_QUEUE_NAME = 'watchBroadcastQueue'; +export const REPLY_QUEUE_NAME = 'watchReplyQueue'; +export const REACTION_QUEUE_NAME = 'watchReactionQueue'; +export const TOMBSTONE_QUEUE_NAME = 'watchTombstoneQueue'; +export const PROFILE_QUEUE_NAME = 'watchProfileQueue'; +export const UPDATE_QUEUE_NAME = 'watchUpdateQueue'; +/** + * Map between announcement type and it's queueName + */ +export const ANNOUNCEMENT_TO_QUEUE_NAME_MAP = new Map([ + [AnnouncementTypeDto.BROADCAST, BROADCAST_QUEUE_NAME], + [AnnouncementTypeDto.REPLY, REPLY_QUEUE_NAME], + [AnnouncementTypeDto.REACTION, REACTION_QUEUE_NAME], + [AnnouncementTypeDto.TOMBSTONE, TOMBSTONE_QUEUE_NAME], + [AnnouncementTypeDto.PROFILE, PROFILE_QUEUE_NAME], + [AnnouncementTypeDto.UPDATE, UPDATE_QUEUE_NAME], +]); +/** + * Map between queue name and it's announcement type + */ +export const QUEUE_NAME_TO_ANNOUNCEMENT_MAP = new Map([ + [BROADCAST_QUEUE_NAME, AnnouncementTypeDto.BROADCAST], + [REPLY_QUEUE_NAME, AnnouncementTypeDto.REPLY], + [REACTION_QUEUE_NAME, AnnouncementTypeDto.REACTION], + [TOMBSTONE_QUEUE_NAME, AnnouncementTypeDto.TOMBSTONE], + [PROFILE_QUEUE_NAME, AnnouncementTypeDto.PROFILE], + [UPDATE_QUEUE_NAME, AnnouncementTypeDto.UPDATE], +]); export const calculateJobId = (jobWithoutId: any): string => { const stringVal = JSON.stringify(jobWithoutId); diff --git a/services/content-watcher/libs/common/src/utils/redis.ts b/services/content-watcher/libs/common/src/utils/redis.ts index 55ca5dd1..4102dd25 100644 --- a/services/content-watcher/libs/common/src/utils/redis.ts +++ b/services/content-watcher/libs/common/src/utils/redis.ts @@ -1,51 +1,43 @@ -export namespace RedisUtils { - /** - * 45 days upper limit to avoid keeping abandoned data forever - */ - export const STORAGE_EXPIRE_UPPER_LIMIT_SECONDS = 45 * 24 * 60 * 60; - /** - * batch Lock expire time which applies during closing operation - */ - export const BATCH_LOCK_EXPIRE_SECONDS = 6; - /** - * To be able to provide mostly unique nonces to submit transactions on chain we would need to check a number of - * temporarily locked keys on redis side and get the first available one. This number defines the number of keys - * we should look into before giving up - */ - export const NUMBER_OF_NONCE_KEYS_TO_CHECK = 50; - /** - * Nonce keys have to get expired shortly so that if any of nonce numbers get skipped we would still have a way to - * submit them after expiration - */ - export const NONCE_KEY_EXPIRE_SECONDS = 2; - const CHAIN_NONCE_KEY = 'chain:nonce'; - const ASSET_DATA_KEY_PREFIX = 'asset:data'; - const ASSET_METADATA_KEY_PREFIX = 'asset:metadata'; - const BATCH_DATA_KEY_PREFIX = 'batch:data'; - const BATCH_METADATA_KEY_PREFIX = 'batch:metadata'; - const LOCK_KEY_PREFIX = 'locked'; - - export function getAssetDataKey(assetId: string) { - return `${ASSET_DATA_KEY_PREFIX}:${assetId}`; - } - - export function getAssetMetadataKey(assetId: string) { - return `${ASSET_METADATA_KEY_PREFIX}:${assetId}`; - } - - export function getBatchDataKey(queueName: string) { - return `${BATCH_DATA_KEY_PREFIX}:${queueName}`; - } - - export function getBatchMetadataKey(queueName: string) { - return `${BATCH_METADATA_KEY_PREFIX}:${queueName}`; - } - - export function getLockKey(suffix: string) { - return `${LOCK_KEY_PREFIX}:${suffix}`; - } - - export function getNonceKey(suffix: string) { - return `${CHAIN_NONCE_KEY}:${suffix}`; - } +/** + * 45 days upper limit to avoid keeping abandoned data forever + */ +export const STORAGE_EXPIRE_UPPER_LIMIT_SECONDS = 45 * 24 * 60 * 60; +/** + * batch Lock expire time which applies during closing operation + */ +export const BATCH_LOCK_EXPIRE_SECONDS = 6; +/** + * To be able to provide mostly unique nonces to submit transactions on chain we would need to check a number of + * temporarily locked keys on redis side and get the first available one. This number defines the number of keys + * we should look into before giving up + */ +export const NUMBER_OF_NONCE_KEYS_TO_CHECK = 50; +/** + * Nonce keys have to get expired shortly so that if any of nonce numbers get skipped we would still have a way to + * submit them after expiration + */ +export const NONCE_KEY_EXPIRE_SECONDS = 2; +const CHAIN_NONCE_KEY = 'chain:nonce'; +const ASSET_DATA_KEY_PREFIX = 'asset:data'; +const ASSET_METADATA_KEY_PREFIX = 'asset:metadata'; +const BATCH_DATA_KEY_PREFIX = 'batch:data'; +const BATCH_METADATA_KEY_PREFIX = 'batch:metadata'; +const LOCK_KEY_PREFIX = 'locked'; +export function getAssetDataKey(assetId: string) { + return `${ASSET_DATA_KEY_PREFIX}:${assetId}`; +} +export function getAssetMetadataKey(assetId: string) { + return `${ASSET_METADATA_KEY_PREFIX}:${assetId}`; +} +export function getBatchDataKey(queueName: string) { + return `${BATCH_DATA_KEY_PREFIX}:${queueName}`; +} +export function getBatchMetadataKey(queueName: string) { + return `${BATCH_METADATA_KEY_PREFIX}:${queueName}`; +} +export function getLockKey(suffix: string) { + return `${LOCK_KEY_PREFIX}:${suffix}`; +} +export function getNonceKey(suffix: string) { + return `${CHAIN_NONCE_KEY}:${suffix}`; } diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 8fa3abb3..b2ce4e69 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -1,126 +1,103 @@ { "name": "content-watcher-service", - "version": "0.1.0", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "content-watcher-service", - "version": "0.1.0", + "version": "0.9.0", "license": "Apache-2.0", "dependencies": { - "@bull-board/api": "^5.8.3", - "@bull-board/express": "^5.8.3", - "@bull-board/nestjs": "^5.8.3", - "@bull-board/ui": "^5.8.3", + "@bull-board/api": "^5.17.1", + "@bull-board/express": "^5.17.1", + "@bull-board/nestjs": "^5.17.1", + "@bull-board/ui": "^5.17.1", "@dsnp/activity-content": "^1.1.0", - "@dsnp/frequency-schemas": "^1.0.2", - "@dsnp/parquetjs": "^1.3.4", - "@frequency-chain/api-augment": "1.7.0", - "@jest/globals": "^29.5.0", - "@liaoliaots/nestjs-redis": "^9.0.5", + "@dsnp/frequency-schemas": "^1.1.0", + "@dsnp/parquetjs": "^1.6.2", + "@frequency-chain/api-augment": "1.11.1", "@multiformats/blake2": "^1.0.13", - "@nestjs/axios": "^2.0.0", - "@nestjs/bullmq": "^10.0.0", - "@nestjs/cli": "^10.1.14", - "@nestjs/common": "^9.4.0", - "@nestjs/config": "^2.3.1", - "@nestjs/core": "^9.4.0", - "@nestjs/event-emitter": "^1.4.1", - "@nestjs/platform-express": "^9.4.0", - "@nestjs/schedule": "^3.0.3", - "@nestjs/swagger": "^7.1.8", - "@nestjs/testing": "^9.4.0", - "@nestjs/typeorm": "^9.0.1", - "@polkadot/api": "^10.9.1", - "@polkadot/api-base": "^10.9.1", - "@polkadot/keyring": "^12.3.2", - "@polkadot/types": "^10.9.1", - "@polkadot/util": "^12.3.2", - "@polkadot/util-crypto": "^12.3.2", + "@nestjs/bullmq": "^10.1.1", + "@nestjs/cli": "^10.3.2", + "@nestjs/common": "^10.3.8", + "@nestjs/config": "^3.2.2", + "@nestjs/core": "^10.3.8", + "@nestjs/event-emitter": "^2.0.4", + "@nestjs/platform-express": "^10.3.8", + "@nestjs/schedule": "^4.0.2", + "@nestjs/swagger": "^7.3.1", + "@polkadot/api": "^10.12.4", + "@polkadot/api-base": "^10.12.4", + "@polkadot/types": "^10.12.4", + "@polkadot/util": "^12.6.2", + "@songkeys/nestjs-redis": "^10.0.0", "@types/multer": "^1.4.7", - "@types/uuid": "^9.0.2", - "axios": "^1.3.6", - "bullmq": "^3.0.0", + "axios": "^1.6.8", + "bullmq": "^5.7.8", "class-transformer": "^0.5.1", - "class-validator": "^0.14.0", + "class-validator": "^0.14.1", "form-data": "^4.0.0", - "ioredis": "^5.3.2", + "ioredis": "^5.4.1", "ipfs-only-hash": "^4.0.0", - "joi": "^17.9.1", + "joi": "^17.13.1", "mime-types": "^2.1.35", "multiformats": "9.9.0", "rxjs": "^7.8.1", "time-constants": "^1.0.3" }, "devDependencies": { - "@polkadot/typegen": "10.9.1", - "@types/express": "^4.17.17", - "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "@types/supertest": "^2.0.12", - "@types/time-constants": "^1.0.0", - "@typescript-eslint/parser": "^7.7.0", - "@typescript-eslint/typescript-estree": "^7.7.0", - "dotenv": "^16.3.1", - "eslint": "^8.42.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^18.0.0", - "eslint-config-prettier": "^8.8.0", - "eslint-import-resolver-typescript": "^3.5.5", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.7.0", - "eslint-plugin-nestjs": "^1.2.3", - "eslint-plugin-prettier": "^5.0.0", - "eslint-plugin-promise": "^6.1.1", - "ioredis-mock": "^8.8.3", - "jest": "^29.5.0", - "license-report": "^6.4.0", - "nock": "^13.3.8", - "prettier": "^3.0.2", - "source-map-support": "^0.5.21", - "supertest": "^6.3.3", - "trace-unhandled": "^2.0.1", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.3", - "ts-node": "^10.9.1", + "@jest/globals": "^29.7.0", + "@nestjs/testing": "^10.3.8", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/node": "^20.12.10", + "@types/supertest": "^6.0.2", + "@types/time-constants": "^1.0.2", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-plugin-jest": "^28.5.0", + "ioredis-mock": "^8.9.0", + "jest": "^29.7.0", + "license-report": "^6.5.0", + "prettier": "^3.2.5", + "supertest": "^7.0.0", + "ts-jest": "^29.1.2", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" - } - }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "typescript": "^5.4.5", + "typescript-eslint": "^7.8.0" } }, "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "license": "Apache-2.0", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@angular-devkit/core": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.0.tgz", - "integrity": "sha512-l1k6Rqm3YM16BEn3CWyQKrk9xfu+2ux7Bw3oS+h1TO4/RoxO2PgHj8LLRh/WNrYVarhaqO7QZ5ePBkXNMkzJ1g==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.1.2.tgz", + "integrity": "sha512-ku+/W/HMCBacSWFppenr9y6Lx8mDuTuQvn1IkTyBLiJOpWnzgVbx9kHDeaDchGa1PwLlJUBBrv27t3qgJOIDPw==", "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", "jsonc-parser": "3.2.0", + "picomatch": "3.0.1", "rxjs": "7.8.1", "source-map": "0.7.4" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -133,60 +110,32 @@ } } }, - "node_modules/@angular-devkit/core/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@angular-devkit/core/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==" - }, - "node_modules/@angular-devkit/core/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/@angular-devkit/schematics": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.0.tgz", - "integrity": "sha512-QMDJXPE0+YQJ9Ap3MMzb0v7rx6ZbBEokmHgpdIjN3eILYmbAdsSGE8HTV8NjS9nKmcyE9OGzFCMb7PFrDTlTAw==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.1.2.tgz", + "integrity": "sha512-8S9RuM8olFN/gwN+mjbuF1CwHX61f0i59EGXz9tXLnKRUTjsRR+8vVMTAmX0dvVAT5fJTG/T69X+HX7FeumdqA==", "dependencies": { - "@angular-devkit/core": "16.2.0", + "@angular-devkit/core": "17.1.2", "jsonc-parser": "3.2.0", - "magic-string": "0.30.1", + "magic-string": "0.30.5", "ora": "5.4.1", "rxjs": "7.8.1" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.2.0.tgz", - "integrity": "sha512-f3HjrDvSrRMvESogLsqsZXsEg//trIBySCHRXCglPrWLVdBbIRctGOhXqZoclRxXimIKUx14zLsOWzDwZG8+HQ==", + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.1.2.tgz", + "integrity": "sha512-bvXykYzSST05qFdlgIzUguNOb3z0hCa8HaTwtqdmQo9aFPf+P+/AC56I64t1iTchMjQtf3JrBQhYM25gUdcGbg==", "dependencies": { - "@angular-devkit/core": "16.2.0", - "@angular-devkit/schematics": "16.2.0", + "@angular-devkit/core": "17.1.2", + "@angular-devkit/schematics": "17.1.2", "ansi-colors": "4.1.3", - "inquirer": "8.2.4", + "inquirer": "9.2.12", "symbol-observable": "4.0.0", "yargs-parser": "21.1.1" }, @@ -194,34 +143,106 @@ "schematics": "bin/schematics.js" }, "engines": { - "node": "^16.14.0 || >=18.10.0", + "node": "^18.13.0 || >=20.9.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics-cli/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/figures": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "dependencies": { + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", - "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "version": "9.2.12", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", + "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", + "@ljharb/through": "^2.3.11", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", "lodash": "^4.17.21", - "mute-stream": "0.0.8", + "mute-stream": "1.0.0", "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.18.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "engines": { + "node": ">=0.12.0" } }, "node_modules/@assemblyscript/loader": { @@ -229,263 +250,1163 @@ "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", "integrity": "sha512-HazVq9zwTVwGmqdwYzu7WyQ6FQVZ7SwET0KKQuKm55jD0IfUpZgN0OPIiZG3zV1iSrVYcN0bdwLRXI/VNCYsUA==" }, - "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "node_modules/@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=6.9.0" + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/code-frame/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==", + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/crc32c": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", + "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/code-frame/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==", + "node_modules/@aws-crypto/crc32c/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/ie11-detection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", + "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" + "tslib": "^1.11.1" } }, - "node_modules/@babel/code-frame/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==", + "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", + "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", "dependencies": { - "color-name": "1.1.3" + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/code-frame/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==" + "node_modules/@aws-crypto/sha1-browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, - "node_modules/@babel/code-frame/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==", - "engines": { - "node": ">=0.8.0" + "node_modules/@aws-crypto/sha256-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", + "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "dependencies": { + "@aws-crypto/ie11-detection": "^3.0.0", + "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^3.0.0", + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/code-frame/node_modules/has-flag": { + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/sha256-js": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", + "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/code-frame/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==", + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", + "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "tslib": "^1.11.1" } }, - "node_modules/@babel/compat-data": { - "version": "7.22.5", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, - "node_modules/@babel/core": { - "version": "7.22.5", - "license": "MIT", + "node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.5", - "@babel/helpers": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" } }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "1.9.0", - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.574.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.574.0.tgz", + "integrity": "sha512-198QLFeJEs3xgCkLcGD8r0IVCR+BTjXGbVpDYC0DCU7vWjINR8igwwnuA5kbCHDALXvWmkX5MVuAlDuawsUn6w==", + "dependencies": { + "@aws-crypto/sha1-browser": "3.0.0", + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sso-oidc": "3.574.0", + "@aws-sdk/client-sts": "3.574.0", + "@aws-sdk/core": "3.572.0", + "@aws-sdk/credential-provider-node": "3.572.0", + "@aws-sdk/middleware-bucket-endpoint": "3.568.0", + "@aws-sdk/middleware-expect-continue": "3.572.0", + "@aws-sdk/middleware-flexible-checksums": "3.572.0", + "@aws-sdk/middleware-host-header": "3.567.0", + "@aws-sdk/middleware-location-constraint": "3.567.0", + "@aws-sdk/middleware-logger": "3.568.0", + "@aws-sdk/middleware-recursion-detection": "3.567.0", + "@aws-sdk/middleware-sdk-s3": "3.572.0", + "@aws-sdk/middleware-signing": "3.572.0", + "@aws-sdk/middleware-ssec": "3.567.0", + "@aws-sdk/middleware-user-agent": "3.572.0", + "@aws-sdk/region-config-resolver": "3.572.0", + "@aws-sdk/signature-v4-multi-region": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@aws-sdk/util-user-agent-browser": "3.567.0", + "@aws-sdk/util-user-agent-node": "3.568.0", + "@aws-sdk/xml-builder": "3.567.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/eventstream-serde-browser": "^2.2.0", + "@smithy/eventstream-serde-config-resolver": "^2.2.0", + "@smithy/eventstream-serde-node": "^2.2.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-blob-browser": "^2.2.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/hash-stream-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/md5-js": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-stream": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "@smithy/util-waiter": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.572.0.tgz", + "integrity": "sha512-S+xhScao5MD79AkrcHmFpEDk+CgoiuB/31WFcTcnrTio5TOUONAaT0QyscOIwRp7BZ7Aez7TBM+loTteJ+TQvg==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.572.0", + "@aws-sdk/middleware-host-header": "3.567.0", + "@aws-sdk/middleware-logger": "3.568.0", + "@aws-sdk/middleware-recursion-detection": "3.567.0", + "@aws-sdk/middleware-user-agent": "3.572.0", + "@aws-sdk/region-config-resolver": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@aws-sdk/util-user-agent-browser": "3.567.0", + "@aws-sdk/util-user-agent-node": "3.568.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.574.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.574.0.tgz", + "integrity": "sha512-WcR8AnFhx7bqhYwfSl3OrF0Pu0LfHGgSOnmmORHqRF7ykguE09M/WUlCCjTGmZjJZ1we3uF5Xg8Jg12eiD+bmw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.574.0", + "@aws-sdk/core": "3.572.0", + "@aws-sdk/credential-provider-node": "3.572.0", + "@aws-sdk/middleware-host-header": "3.567.0", + "@aws-sdk/middleware-logger": "3.568.0", + "@aws-sdk/middleware-recursion-detection": "3.567.0", + "@aws-sdk/middleware-user-agent": "3.572.0", + "@aws-sdk/region-config-resolver": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@aws-sdk/util-user-agent-browser": "3.567.0", + "@aws-sdk/util-user-agent-node": "3.568.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.574.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.574.0.tgz", + "integrity": "sha512-WNDSG9nipap/L1gGDkCQvU2u413HmVxMJKr41lBCibioz42Z4i6XkCr1etYwIjuVfGF6QPrsEsYLqRwlAC/BQg==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sso-oidc": "3.574.0", + "@aws-sdk/core": "3.572.0", + "@aws-sdk/credential-provider-node": "3.572.0", + "@aws-sdk/middleware-host-header": "3.567.0", + "@aws-sdk/middleware-logger": "3.568.0", + "@aws-sdk/middleware-recursion-detection": "3.567.0", + "@aws-sdk/middleware-user-agent": "3.572.0", + "@aws-sdk/region-config-resolver": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@aws-sdk/util-user-agent-browser": "3.567.0", + "@aws-sdk/util-user-agent-node": "3.568.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.572.0.tgz", + "integrity": "sha512-DBmf94qfN0dfaLl5EnNcq6TakWfOtVXYifHoTbX+VBwESj3rlY4W+W4mAnvBgAqDjlLFy7bBljmx+vnjnV73lg==", + "dependencies": { + "@smithy/core": "^1.4.2", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "fast-xml-parser": "4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.568.0.tgz", + "integrity": "sha512-MVTQoZwPnP1Ev5A7LG+KzeU6sCB8BcGkZeDT1z1V5Wt7GPq0MgFQTSSjhImnB9jqRSZkl1079Bt3PbO6lfIS8g==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.568.0.tgz", + "integrity": "sha512-gL0NlyI2eW17hnCrh45hZV+qjtBquB+Bckiip9R6DIVRKqYcoILyiFhuOgf2bXeF23gVh6j18pvUvIoTaFWs5w==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.572.0.tgz", + "integrity": "sha512-anlYZnpmVkfp9Gan+LcEkQvmRf/m0KcbR11th8sBEyI5lxMaHKXhnAtC/hEGT7e3L6rgNOrFYTPuSvllITD/Pg==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.568.0", + "@aws-sdk/credential-provider-http": "3.568.0", + "@aws-sdk/credential-provider-ini": "3.572.0", + "@aws-sdk/credential-provider-process": "3.572.0", + "@aws-sdk/credential-provider-sso": "3.572.0", + "@aws-sdk/credential-provider-web-identity": "3.568.0", + "@aws-sdk/types": "3.567.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.572.0.tgz", + "integrity": "sha512-S6C/S6xYesDakEuzYvlY1DMMKLtKQxdbbygq3hfeG2R0jUt9KpRLsQXK8qrBuVCKa3WcnjN/30hp4g/iUWFU/w==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.572.0", + "@aws-sdk/core": "3.572.0", + "@aws-sdk/credential-provider-node": "3.572.0", + "@aws-sdk/middleware-host-header": "3.567.0", + "@aws-sdk/middleware-logger": "3.568.0", + "@aws-sdk/middleware-recursion-detection": "3.567.0", + "@aws-sdk/middleware-user-agent": "3.572.0", + "@aws-sdk/region-config-resolver": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@aws-sdk/util-user-agent-browser": "3.567.0", + "@aws-sdk/util-user-agent-node": "3.568.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/client-sts": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.572.0.tgz", + "integrity": "sha512-jCQuH2qkbWoSY4wckLSfzf3OPh7zc7ZckEbIGGVUQar/JVff6EIbpQ+uNG29DDEOpdPPd8rrJsVuUlA/nvJdXA==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sso-oidc": "3.572.0", + "@aws-sdk/core": "3.572.0", + "@aws-sdk/credential-provider-node": "3.572.0", + "@aws-sdk/middleware-host-header": "3.567.0", + "@aws-sdk/middleware-logger": "3.568.0", + "@aws-sdk/middleware-recursion-detection": "3.567.0", + "@aws-sdk/middleware-user-agent": "3.572.0", + "@aws-sdk/region-config-resolver": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@aws-sdk/util-user-agent-browser": "3.567.0", + "@aws-sdk/util-user-agent-node": "3.568.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.572.0.tgz", + "integrity": "sha512-05KzbAp77fEiQXqMeodXeMbT83FOqSyBrfSEMz6U8uBXeJCy8zPUjN3acqcbG55/HNJHUvg1cftqzy+fUz71gA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.568.0", + "@aws-sdk/credential-provider-process": "3.572.0", + "@aws-sdk/credential-provider-sso": "3.572.0", + "@aws-sdk/credential-provider-web-identity": "3.568.0", + "@aws-sdk/types": "3.567.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "3.572.0" } }, - "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.572.0.tgz", + "integrity": "sha512-hXcOytf0BadSm/MMy7MV8mmY0+Jv3mkavsHNBx0R82hw5ollD0I3JyOAaCtdUpztF0I72F8K+q8SpJQZ+EwArw==", "dependencies": { - "@babel/types": "^7.23.0", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" + "@aws-sdk/types": "3.567.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.572.0.tgz", + "integrity": "sha512-iIlnpJiDXFp3XC4hJNSiNurnU24mr3iLB3HoNa9efr944bo6XBl9FQdk3NssIkqzSmgyoB2CEUx/daBHz4XSow==", + "dependencies": { + "@aws-sdk/client-sso": "3.572.0", + "@aws-sdk/token-providers": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.0.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "license": "MIT" - }, - "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.572.0.tgz", + "integrity": "sha512-S6C/S6xYesDakEuzYvlY1DMMKLtKQxdbbygq3hfeG2R0jUt9KpRLsQXK8qrBuVCKa3WcnjN/30hp4g/iUWFU/w==", + "peer": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.572.0", + "@aws-sdk/core": "3.572.0", + "@aws-sdk/credential-provider-node": "3.572.0", + "@aws-sdk/middleware-host-header": "3.567.0", + "@aws-sdk/middleware-logger": "3.568.0", + "@aws-sdk/middleware-recursion-detection": "3.567.0", + "@aws-sdk/middleware-user-agent": "3.572.0", + "@aws-sdk/region-config-resolver": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@aws-sdk/util-user-agent-browser": "3.567.0", + "@aws-sdk/util-user-agent-node": "3.568.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/client-sts": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.572.0.tgz", + "integrity": "sha512-jCQuH2qkbWoSY4wckLSfzf3OPh7zc7ZckEbIGGVUQar/JVff6EIbpQ+uNG29DDEOpdPPd8rrJsVuUlA/nvJdXA==", + "peer": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sso-oidc": "3.572.0", + "@aws-sdk/core": "3.572.0", + "@aws-sdk/credential-provider-node": "3.572.0", + "@aws-sdk/middleware-host-header": "3.567.0", + "@aws-sdk/middleware-logger": "3.568.0", + "@aws-sdk/middleware-recursion-detection": "3.567.0", + "@aws-sdk/middleware-user-agent": "3.572.0", + "@aws-sdk/region-config-resolver": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@aws-sdk/util-user-agent-browser": "3.567.0", + "@aws-sdk/util-user-agent-node": "3.568.0", + "@smithy/config-resolver": "^2.2.0", + "@smithy/core": "^1.4.2", + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/hash-node": "^2.2.0", + "@smithy/invalid-dependency": "^2.2.0", + "@smithy/middleware-content-length": "^2.2.0", + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-body-length-browser": "^2.2.0", + "@smithy/util-body-length-node": "^2.3.0", + "@smithy/util-defaults-mode-browser": "^2.2.1", + "@smithy/util-defaults-mode-node": "^2.3.1", + "@smithy/util-endpoints": "^1.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.572.0.tgz", + "integrity": "sha512-IkSu8p32tQZhKqwmfLZLGfYwNhsS/HUQBLnDMHJlr9VifmDqlTurcr+DwMCaMimuFhcLeb45vqTymKf/ro/OBw==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "3.572.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.5", - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.568.0.tgz", + "integrity": "sha512-ZJSmTmoIdg6WqAULjYzaJ3XcbgBzVy36lir6Y0UBMRGaxDgos1AARuX6EcYzXOl+ksLvxt/xMQ+3aYh1LWfKSw==", "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "@aws-sdk/types": "3.567.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@aws-sdk/client-sts": "^3.568.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "license": "ISC", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.568.0.tgz", + "integrity": "sha512-uc/nbSpXv64ct/wV3Ksz0/bXAsEtXuoZu5J9FTcFnM7c2MSofa0YQrtrJ8cG65uGbdeiFoJwPA048BTG/ilhCA==", "dependencies": { - "yallist": "^3.0.2" + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.572.0.tgz", + "integrity": "sha512-+NKWVK295rOEANU/ohqEfNjkcEdZao7z6HxkMXX4gu4mDpSsVU8WhYr5hp5k3PUhtaiPU8M1rdfQBrZQc4uttw==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "license": "ISC" - }, - "node_modules/@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==", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.572.0.tgz", + "integrity": "sha512-ysblGDRn1yy8TlKUrwhnFbl3RuMfbVW1rbtePClEYpC/1u9MsqPmm/fmWJJGKat7NclnsgpQyfSQ64DCuaEedg==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@aws-crypto/crc32c": "3.0.0", + "@aws-sdk/types": "3.567.0", + "@smithy/is-array-buffer": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@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==", + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.567.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.567.0.tgz", + "integrity": "sha512-zQHHj2N3in9duKghH7AuRNrOMLnKhW6lnmb7dznou068DJtDr76w475sHp2TF0XELsOGENbbBsOlN/S5QBFBVQ==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@aws-sdk/types": "3.567.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "license": "MIT", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.567.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.567.0.tgz", + "integrity": "sha512-XiGTH4VxrJ5fj6zeF6UL5U5EuJwLqj9bHW5pB+EKfw0pmbnyqfRdYNt46v4GsQql2iVOq1Z/Fiv754nIItBI/A==", "dependencies": { - "@babel/types": "^7.22.5" + "@aws-sdk/types": "3.567.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.22.5", - "license": "MIT", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.568.0.tgz", + "integrity": "sha512-BinH72RG7K3DHHC1/tCulocFv+ZlQ9SrPF9zYT0T1OT95JXuHhB7fH8gEABrc6DAtOdJJh2fgxQjPy5tzPtsrA==", "dependencies": { - "@babel/types": "^7.22.5" + "@aws-sdk/types": "3.567.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.22.5", - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-module-imports": "^7.22.5", - "@babel/helper-simple-access": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.567.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.567.0.tgz", + "integrity": "sha512-rFk3QhdT4IL6O/UWHmNdjJiURutBCy+ogGqaNHf/RELxgXH3KmYorLwCe0eFb5hq8f6vr3zl4/iH7YtsUOuo1w==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=6.9.0" + "node": ">=16.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "license": "MIT", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.572.0.tgz", + "integrity": "sha512-ygQL1G2hWoJXkUGL/Xr5q9ojXCH8hgt/oKsxJtc5U8ZXw3SRlL6pCVE7+aiD0l8mgIGbW0vrL08Oc/jYWlakdw==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-signing": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.572.0.tgz", + "integrity": "sha512-/pEVgHnf8LsTG0hu9yqqvmLMknlKO5c19NM3J9qTWGLPfySi8tWrFuREAFKAxqJFgDw1IdFWd+dXIkodpbGwew==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.567.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.567.0.tgz", + "integrity": "sha512-lhpBwFi3Tcw+jlOdaCsg3lCAg4oOSJB00bW/aLTFeZWutwi9VexMmsddZllx99lN+LDeCjryNyVd2TCRCKwYhQ==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.572.0.tgz", + "integrity": "sha512-R4bBbLp1ywtF1kJoOX1juDMztKPWeQHNj6XuTvtruFDn1RdfnBlbM3+9rguRfH5s4V+xfl8SSWchnyo2cI00xg==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@aws-sdk/util-endpoints": "3.572.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.572.0.tgz", + "integrity": "sha512-xkZMIxek44F4YW5r9otD1O5Y/kDkgAb6JNJePkP1qPVojrkCmin3OFYAOZgGm+T4DZAQ5rWhpaqTAWmnRumYfw==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.572.0.tgz", + "integrity": "sha512-FD6FIi8py1ZAR53NjD2VXKDvvQUhhZu7CDUfC9gjAa7JDtv+rJvM9ZuoiQjaDnzzqYxTr4pKqqjLsd6+8BCSWA==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.572.0", + "@aws-sdk/types": "3.567.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/signature-v4": "^2.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.567.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.567.0.tgz", + "integrity": "sha512-JBznu45cdgQb8+T/Zab7WpBmfEAh77gsk99xuF4biIb2Sw1mdseONdoGDjEJX57a25TzIv/WUJ2oABWumckz1A==", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.572.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.572.0.tgz", + "integrity": "sha512-AIEC7ItIWBqkJLtqcSd0HG8tvdh3zVwqnKPHNrcfFay0Xonqx3p/qTCDwGosh5CM5hDGzyOSRA5PkacEDBTz9w==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/types": "^2.12.0", + "@smithy/util-endpoints": "^1.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.567.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.567.0.tgz", + "integrity": "sha512-cqP0uXtZ7m7hRysf3fRyJwcY1jCgQTpJy7BHB5VpsE7DXlXHD5+Ur5L42CY7UrRPrB6lc6YGFqaAOs5ghMcLyA==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/types": "^2.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.568.0.tgz", + "integrity": "sha512-NVoZoLnKF+eXPBvXg+KqixgJkPSrerR6Gqmbjwqbv14Ini+0KNKB0/MXas1mDGvvEgtNkHI/Cb9zlJ3KXpti2A==", + "dependencies": { + "@aws-sdk/types": "3.567.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.567.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.567.0.tgz", + "integrity": "sha512-Db25jK9sZdGa7PEQTdm60YauUVbeYGsSEMQOHGP6ifbXfCknqgkPgWV16DqAKJUsbII0xgkJ9LpppkmYal3K/g==", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@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==", + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-simple-access": { + "node_modules/@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==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", + "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/core/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@babel/core/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", + "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@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==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@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==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@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==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, "dependencies": { "@babel/types": "^7.22.5" }, @@ -493,59 +1414,119 @@ "node": ">=6.9.0" } }, + "node_modules/@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==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", + "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-simple-access": "^7.24.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", + "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@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.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "license": "MIT", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@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.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.5", - "license": "MIT", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.22.5", - "license": "MIT", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", + "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", + "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", + "@babel/helper-validator-identifier": "^7.24.5", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -616,9 +1597,10 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -628,7 +1610,9 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -638,7 +1622,9 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -648,7 +1634,9 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -658,7 +1646,9 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -668,7 +1658,9 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -677,10 +1669,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "license": "MIT", + "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==", + "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -691,7 +1685,9 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -701,7 +1697,9 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -711,7 +1709,9 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -721,7 +1721,9 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -731,7 +1733,9 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -741,7 +1745,9 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -751,7 +1757,9 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -763,10 +1771,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "license": "MIT", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", + "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -775,336 +1785,137 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.22.3", - "license": "MIT", - "peer": true, - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", + "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", + "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", - "debug": "^4.1.0", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/types": "^7.24.5", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT" - }, - "node_modules/@bull-board/api": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@bull-board/api/-/api-5.8.3.tgz", - "integrity": "sha512-xiVSXc99WQIhrsXTo/rCVxmV9uphyJA4a6ytBP7RyTGO/+IGTg7zLo2yrnYdv3p8RYw4YzdJ4aY1E+MgP5mj8Q==", - "dependencies": { - "redis-info": "^3.0.8" - }, - "peerDependencies": { - "@bull-board/ui": "5.8.3" - } - }, - "node_modules/@bull-board/express": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@bull-board/express/-/express-5.8.3.tgz", - "integrity": "sha512-zZicV35gRfnojtx+Ypg02xuk/JSSmfDaMUgGbrXfXKvq1OdQCKPkhIa9uPyHP6a7d/QLHKqzQFgJknqgQVBkxw==", - "dependencies": { - "@bull-board/api": "5.8.3", - "@bull-board/ui": "5.8.3", - "ejs": "3.1.7", - "express": "4.17.3" - } - }, - "node_modules/@bull-board/express/node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@bull-board/express/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@bull-board/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@bull-board/express/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@bull-board/express/node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" - }, - "node_modules/@bull-board/express/node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.19.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.9.7", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "ms": "2.1.2" }, "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/@bull-board/express/node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "node": ">=6.0" }, - "engines": { - "node": ">= 0.8" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@bull-board/express/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, + "node_modules/@babel/traverse/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, "engines": { - "node": ">= 0.6" + "node": ">=4" } }, - "node_modules/@bull-board/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "node_modules/@bull-board/express/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "node_modules/@babel/types": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "dev": true, "dependencies": { - "ee-first": "1.1.1" + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", + "to-fast-properties": "^2.0.0" }, "engines": { - "node": ">= 0.8" - } - }, - "node_modules/@bull-board/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/@bull-board/express/node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6.9.0" } }, - "node_modules/@bull-board/express/node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } + "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 }, - "node_modules/@bull-board/express/node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "node_modules/@bull-board/api": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@bull-board/api/-/api-5.17.1.tgz", + "integrity": "sha512-KQaH3z2YJxN2h+Kh4ZJ341NNEFwrsNeZAu9FtQX8h4G0nkSw3FSlgWDO6UrN0b448+co/gWTT6qZz0st2fFmPg==", "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "1.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "redis-info": "^3.0.8" }, - "engines": { - "node": ">= 0.8.0" + "peerDependencies": { + "@bull-board/ui": "5.17.1" } }, - "node_modules/@bull-board/express/node_modules/send/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==" - }, - "node_modules/@bull-board/express/node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "node_modules/@bull-board/express": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@bull-board/express/-/express-5.17.1.tgz", + "integrity": "sha512-3aWDP7UPdnUj2SYdxKnu/Rtb/1a0KnfKu7a/iPUBVPQQJN8CQzcdcYG3BNhFnrbY8f3+efI1o0eUkgdkJYB1aA==", "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/@bull-board/express/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==", - "engines": { - "node": ">= 0.6" + "@bull-board/api": "5.17.1", + "@bull-board/ui": "5.17.1", + "ejs": "^3.1.7", + "express": "^4.17.3" } }, "node_modules/@bull-board/nestjs": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@bull-board/nestjs/-/nestjs-5.8.3.tgz", - "integrity": "sha512-Jyr9EhHrsv1OsjZlJeInHrTR5u3UVoEYbT2WLRr1YmQmQlqIbjMjH2B7G+E6JKk5jggVEJkLi06gSN8lV9y6uw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@bull-board/nestjs/-/nestjs-5.17.1.tgz", + "integrity": "sha512-kZDwOP7EQqNfJAnJgWIJ9EtuAXuoyZH3FTT+KZFhO/qsSAWP4tvspFznW7FoKJy7tZkyzJzRjl82mcsUYNpDNQ==", "dependencies": { "@nestjs/bull-shared": "^10.0.0" }, "peerDependencies": { - "@bull-board/api": "^5.8.3", - "@bull-board/express": "^5.8.3", + "@bull-board/api": "^5.17.1", + "@bull-board/express": "^5.17.1", "@nestjs/common": "^9.0.0 || ^10.0.0", "@nestjs/core": "^9.0.0 || ^10.0.0", - "reflect-metadata": "^0.1.13", + "reflect-metadata": "^0.1.13 || ^0.2.0", "rxjs": "^7.8.1" } }, "node_modules/@bull-board/ui": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-5.8.3.tgz", - "integrity": "sha512-pQhmboukRZccs6WaTkRVGPZucL1ND8/+7JdK1pNKa0RVGVkpshH7QaHdfI30DR0h/Jv/VFaenWV1vS3KnvqeHw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@bull-board/ui/-/ui-5.17.1.tgz", + "integrity": "sha512-/BO4C454w7ar5ZaHfcThEY2A2SYD9YkQkBizq8o9sjglVctpnnnh37YY2iOVRZCZFXMKIe7t05Kd9/9p3IPvSw==", "dependencies": { - "@bull-board/api": "5.8.3" + "@bull-board/api": "5.17.1" } }, "node_modules/@colors/colors": { @@ -1118,7 +1929,8 @@ }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1126,6 +1938,15 @@ "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==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@dsnp/activity-content": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@dsnp/activity-content/-/activity-content-1.1.0.tgz", @@ -1145,38 +1966,30 @@ } }, "node_modules/@dsnp/frequency-schemas": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@dsnp/frequency-schemas/-/frequency-schemas-1.0.2.tgz", - "integrity": "sha512-+u2Fwv9aYbMn7MI5LbiDn92dWK+YxcJJEwdy6r/wdQwFVz/jZtE5lR56KqPYS2piun/vINMJU+HNZUVYL4zkOg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@dsnp/frequency-schemas/-/frequency-schemas-1.1.0.tgz", + "integrity": "sha512-Su7FufZRisQMxpVmVkbXwnnJDqgxAT7mJV3b2lvUDT4FRFZwqlOB5bnUBmrgZGF7Ru8RyrFOmI9K/5R4y2egHA==", "dependencies": { - "@frequency-chain/api-augment": "0.0.0-45e306", + "@frequency-chain/api-augment": "^1.10.0", "@polkadot/api": "^10.7.3", "json-stringify-pretty-compact": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^5.0.4" + "ts-node": "^10.9.2", + "typescript": "^5.3.3" }, "optionalDependencies": { "@dsnp/parquetjs": "^1.3.0" } }, - "node_modules/@dsnp/frequency-schemas/node_modules/@frequency-chain/api-augment": { - "version": "0.0.0-45e306", - "resolved": "https://registry.npmjs.org/@frequency-chain/api-augment/-/api-augment-0.0.0-45e306.tgz", - "integrity": "sha512-VzZIFwMX8LaY6dJfzsJ6t9WKZ28DzGA4+lEKys0OR6O+CIAV/fVWNb8FaHG9c6It0GmSz2Os0ehvn5p/rgWfug==", - "dependencies": { - "@polkadot/api": "^10.7.3", - "@polkadot/rpc-provider": "^10.7.3", - "@polkadot/types": "^10.7.3" - } - }, "node_modules/@dsnp/parquetjs": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@dsnp/parquetjs/-/parquetjs-1.3.4.tgz", - "integrity": "sha512-DKRtMi+EZaoF/IUalz0UhSqRJ4P2/SCcL1aTwCJ5ZAgZGKFHKvOUYcCjM2XrMlsykfKcL3IMlJ4EZZONCGmRIQ==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@dsnp/parquetjs/-/parquetjs-1.6.2.tgz", + "integrity": "sha512-xlhCWVC5yl5zm7epSSm43OLh8ZKi/sk9zv01Kv8CYCkKFJgPQPFwNFobZ0XAEiD+xtexf8MOc7p7TAzm0pHweQ==", "dependencies": { + "@aws-sdk/client-s3": "^3.489.0", "@types/long": "^4.0.2", "@types/node-int64": "^0.4.29", "@types/thrift": "^0.10.11", + "brotli-wasm": "^2.0.1", "browserify-zlib": "^0.2.0", "bson": "4.6.3", "cross-fetch": "^3.1.4", @@ -1185,17 +1998,17 @@ "snappyjs": "^0.6.1", "thrift": "0.16.0", "varint": "^6.0.0", - "wasm-brotli": "^2.0.2", "xxhash-wasm": "^1.0.2" }, "engines": { - "node": ">=16.15.1" + "node": ">=18.18.2" } }, "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", "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -1238,6 +2051,51 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/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, + "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/@eslint/eslintrc/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@eslint/eslintrc/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 + }, + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/@eslint/js": { "version": "8.57.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", @@ -1248,21 +2106,24 @@ } }, "node_modules/@frequency-chain/api-augment": { - "version": "1.7.0", - "license": "Apache-2.0", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@frequency-chain/api-augment/-/api-augment-1.11.1.tgz", + "integrity": "sha512-CzVjeGrWl8tbTavygZLUICrncjCC54hM5ioJU1Og2OPoX2P4GYf8xoks8MIyj1yOrYX++mzM6Uf0+nCh77QyFw==", "dependencies": { - "@polkadot/api": "^10.7.3", - "@polkadot/rpc-provider": "^10.7.3", - "@polkadot/types": "^10.7.3" + "@polkadot/api": "^10.9.1", + "@polkadot/rpc-provider": "^10.9.1", + "@polkadot/types": "^10.9.1" } }, "node_modules/@hapi/hoek": { "version": "9.3.0", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" }, "node_modules/@hapi/topo": { "version": "5.1.0", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", "dependencies": { "@hapi/hoek": "^9.0.0" } @@ -1281,10 +2142,34 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "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", "engines": { "node": ">=12.22" }, @@ -1307,43 +2192,141 @@ }, "node_modules/@ioredis/commands": { "version": "1.2.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "license": "ISC", + "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==", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "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": ">=8" + "node": ">=12" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, + "node_modules/@isaacs/cliui/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==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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==" + }, + "node_modules/@isaacs/cliui/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==", + "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/@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==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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==", + "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/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, "engines": { "node": ">=8" } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1354,7 +2337,9 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -1364,7 +2349,9 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { "version": "2.3.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "dependencies": { "p-try": "^2.0.0" }, @@ -1377,7 +2364,9 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -1387,28 +2376,39 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, "engines": { "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, "engines": { "node": ">=8" } }, "node_modules/@jest/console": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1416,36 +2416,37 @@ } }, "node_modules/@jest/core": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -1462,78 +2463,89 @@ } }, "node_modules/@jest/environment": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, "dependencies": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, "dependencies": { - "jest-get-type": "^29.4.3" + "jest-get-type": "^29.6.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, - "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", @@ -1541,13 +2553,13 @@ "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1565,44 +2577,45 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jest/reporters/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "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/@jest/schemas": { - "version": "29.4.3", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "dependencies": { - "@sinclair/typebox": "^0.25.16" + "@sinclair/typebox": "^0.27.8" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.4.3", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, - "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", + "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" }, @@ -1610,35 +2623,14 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/source-map/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jest/source-map/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, "node_modules/@jest/test-result": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" }, @@ -1647,13 +2639,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/test-result": "^29.5.0", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1661,20 +2654,22 @@ } }, "node_modules/@jest/transform": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1684,30 +2679,13 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "license": "MIT" - }, - "node_modules/@jest/transform/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, "node_modules/@jest/types": { - "version": "29.5.0", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", @@ -1719,83 +2697,159 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "license": "MIT", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "license": "MIT", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "license": "MIT", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", - "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "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==", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "license": "MIT", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@kessler/tableify": { "version": "1.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@kessler/tableify/-/tableify-1.0.2.tgz", + "integrity": "sha512-e4psVV9Fe2eBfS9xK2rzQ9lE5xS4tARm7EJzDb6sVZy3F+EMyHJ67i0NdBVR9BRyQx7YhogMCbB6R1QwXuBxMg==", + "dev": true }, - "node_modules/@liaoliaots/nestjs-redis": { - "version": "9.0.5", - "license": "MIT", + "node_modules/@ljharb/through": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", "dependencies": { - "tslib": "2.4.1" + "call-bind": "^1.0.7" }, "engines": { - "node": ">=12.22.0" - }, - "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/core": "^9.0.0", - "ioredis": "^5.0.0" + "node": ">= 0.4" } }, - "node_modules/@liaoliaots/nestjs-redis/node_modules/tslib": { - "version": "2.4.1", - "license": "0BSD" - }, "node_modules/@lukeed/csprng": { "version": "1.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", "engines": { "node": ">=8" } }, + "node_modules/@microsoft/tsdoc": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", + "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==" + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", + "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz", + "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz", + "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz", + "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz", + "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz", + "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@multiformats/base-x": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@multiformats/base-x/-/base-x-4.0.1.tgz", @@ -1810,21 +2864,12 @@ "multiformats": "^9.5.4" } }, - "node_modules/@nestjs/axios": { - "version": "2.0.0", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", - "axios": "^1.3.1", - "reflect-metadata": "^0.1.12", - "rxjs": "^6.0.0 || ^7.0.0" - } - }, "node_modules/@nestjs/bull-shared": { - "version": "10.0.0", - "license": "MIT", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/bull-shared/-/bull-shared-10.1.1.tgz", + "integrity": "sha512-su7eThDrSz1oQagEi8l+1CyZ7N6nMgmyAX0DuZoXqT1KEVEDqGX7x80RlPVF60m/8SYOskckGMjJROSfNQcErw==", "dependencies": { - "tslib": "2.5.3" + "tslib": "2.6.2" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", @@ -1832,54 +2877,55 @@ } }, "node_modules/@nestjs/bullmq": { - "version": "10.0.0", - "license": "MIT", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/bullmq/-/bullmq-10.1.1.tgz", + "integrity": "sha512-afYx1wYCKtXEu1p0S1+qw2o7QaZWr/EQgF7Wkt3YL8RBIECy5S4C450gv/cRGd8EZjlt6bw8hGCLqR2Q5VjHpQ==", "dependencies": { - "@nestjs/bull-shared": "^10.0.0", - "tslib": "2.5.3" + "@nestjs/bull-shared": "^10.1.1", + "tslib": "2.6.2" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", - "bullmq": "^3.0.0" + "bullmq": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, "node_modules/@nestjs/cli": { - "version": "10.1.14", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.1.14.tgz", - "integrity": "sha512-oxfoebzrq6g+MKc6FRx2O8D86Vk0ViEmlP4B1E3dzwC3X5yjxlA1IDulLrVz3VIpGjuyuXmrQjjd8l0NUVZiKg==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", + "integrity": "sha512-aWmD1GLluWrbuC4a1Iz/XBk5p74Uj6nIVZj6Ov03JbTfgtWqGFLtXuMetvzMiHxfrHehx/myt2iKAPRhKdZvTg==", "dependencies": { - "@angular-devkit/core": "16.2.0", - "@angular-devkit/schematics": "16.2.0", - "@angular-devkit/schematics-cli": "16.2.0", + "@angular-devkit/core": "17.1.2", + "@angular-devkit/schematics": "17.1.2", + "@angular-devkit/schematics-cli": "17.1.2", "@nestjs/schematics": "^10.0.1", "chalk": "4.1.2", - "chokidar": "3.5.3", + "chokidar": "3.6.0", "cli-table3": "0.6.3", "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "8.0.0", + "fork-ts-checker-webpack-plugin": "9.0.2", + "glob": "10.3.10", "inquirer": "8.2.6", "node-emoji": "1.11.0", "ora": "5.4.1", - "os-name": "4.0.1", "rimraf": "4.4.1", "shelljs": "0.8.5", "source-map-support": "0.5.21", "tree-kill": "1.2.2", "tsconfig-paths": "4.2.0", "tsconfig-paths-webpack-plugin": "4.1.0", - "typescript": "5.1.6", - "webpack": "5.88.2", + "typescript": "5.3.3", + "webpack": "5.90.1", "webpack-node-externals": "3.0.0" }, "bin": { "nest": "bin/nest.js" }, "engines": { - "node": ">= 16" + "node": ">= 16.14" }, "peerDependencies": { - "@swc/cli": "^0.1.62", + "@swc/cli": "^0.1.62 || ^0.3.0", "@swc/core": "^1.3.62" }, "peerDependenciesMeta": { @@ -1891,68 +2937,91 @@ } } }, - "node_modules/@nestjs/cli/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==", + "node_modules/@nestjs/cli/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==", "dependencies": { - "balanced-match": "^1.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" } }, - "node_modules/@nestjs/cli/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, + "node_modules/@nestjs/cli/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4.0" } }, - "node_modules/@nestjs/cli/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dependencies": { - "brace-expansion": "^2.0.1" + "node_modules/@nestjs/cli/node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=14.17" } }, - "node_modules/@nestjs/cli/node_modules/rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", + "node_modules/@nestjs/cli/node_modules/webpack": { + "version": "5.90.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz", + "integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==", "dependencies": { - "glob": "^9.2.0" + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "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.9", + "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.0", + "webpack-sources": "^3.2.3" }, "bin": { - "rimraf": "dist/cjs/src/bin.js" + "webpack": "bin/webpack.js" }, "engines": { - "node": ">=14" + "node": ">=10.13.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@nestjs/common": { - "version": "9.4.3", - "license": "MIT", - "dependencies": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/@nestjs/common": { + "version": "10.3.8", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.8.tgz", + "integrity": "sha512-P+vPEIvqx2e+fonsYVlFXKvoChyJ8Tq+lfpqdVFqblovHbFr3kZ/nYX0cPs+XuW6bnRT8tz0SSR9XBGU43kJhw==", + "dependencies": { "iterare": "1.2.1", - "tslib": "2.5.3", + "tslib": "2.6.2", "uid": "2.0.2" }, "funding": { @@ -1960,16 +3029,12 @@ "url": "https://opencollective.com/nest" }, "peerDependencies": { - "cache-manager": "<=5", "class-transformer": "*", "class-validator": "*", - "reflect-metadata": "^0.1.12", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { - "cache-manager": { - "optional": true - }, "class-transformer": { "optional": true }, @@ -1979,40 +3044,31 @@ } }, "node_modules/@nestjs/config": { - "version": "2.3.3", - "license": "MIT", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.2.tgz", + "integrity": "sha512-vGICPOui5vE6kPz1iwQ7oCnp3qWgqxldPmBQ9onkVoKlBtyc83KJCr7CjuVtf4OdovMAVcux1d8Q6jglU2ZphA==", "dependencies": { - "dotenv": "16.1.4", + "dotenv": "16.4.5", "dotenv-expand": "10.0.0", "lodash": "4.17.21", - "uuid": "9.0.0" + "uuid": "9.0.1" }, "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", - "reflect-metadata": "^0.1.13", - "rxjs": "^6.0.0 || ^7.2.0" - } - }, - "node_modules/@nestjs/config/node_modules/dotenv": { - "version": "16.1.4", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "rxjs": "^7.1.0" } }, "node_modules/@nestjs/core": { - "version": "9.4.3", + "version": "10.3.8", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.8.tgz", + "integrity": "sha512-AxF4tpYLDNn5Wfb3C4bNaaHJ4pREH5FJrSisR2A5zkYpQFORFs0Tc36lOFPMwBTy8Iv2wUwWLUVc5ftBnxEv4w==", "hasInstallScript": true, - "license": "MIT", "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "3.2.0", - "tslib": "2.5.3", + "tslib": "2.6.2", "uid": "2.0.2" }, "funding": { @@ -2020,11 +3076,11 @@ "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/microservices": "^9.0.0", - "@nestjs/platform-express": "^9.0.0", - "@nestjs/websockets": "^9.0.0", - "reflect-metadata": "^0.1.12", + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { @@ -2040,26 +3096,26 @@ } }, "node_modules/@nestjs/event-emitter": { - "version": "1.4.2", - "license": "MIT", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.0.4.tgz", + "integrity": "sha512-quMiw8yOwoSul0pp3mOonGz8EyXWHSBTqBy8B0TbYYgpnG1Ix2wGUnuTksLWaaBiiOTDhciaZ41Y5fJZsSJE1Q==", "dependencies": { "eventemitter2": "6.4.9" }, "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", - "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0", - "reflect-metadata": "^0.1.12" + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, "node_modules/@nestjs/mapped-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz", - "integrity": "sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", + "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", "class-transformer": "^0.4.0 || ^0.5.0", "class-validator": "^0.13.0 || ^0.14.0", - "reflect-metadata": "^0.1.12" + "reflect-metadata": "^0.1.12 || ^0.2.0" }, "peerDependenciesMeta": { "class-transformer": { @@ -2071,152 +3127,77 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "9.4.3", - "license": "MIT", + "version": "10.3.8", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.8.tgz", + "integrity": "sha512-sifLoxgEJvAgbim1UuW6wyScMfkS9SVQRH+lN33N/9ZvZSjO6NSDLOe+wxqsnZkia+QrjFC0qy0ITRAsggfqbg==", "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", - "express": "4.18.2", + "express": "4.19.2", "multer": "1.4.4-lts.1", - "tslib": "2.5.3" + "tslib": "2.6.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/core": "^9.0.0" + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" } }, "node_modules/@nestjs/schedule": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-3.0.3.tgz", - "integrity": "sha512-xsMA4dmP3LcW3rt2iMPfm88bDbCj/hLuDsLrKmJQlbnxyCYtBwLtmu/4cSfZELLM7pTDT+E8QDAqGwhYyUUjxg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-4.0.2.tgz", + "integrity": "sha512-po9oauE7fO0CjhDKvVC2tzEgjOUwhxYoIsXIVkgfu+xaDMmzzpmXY2s1LT4oP90Z+PaTtPoAHmhslnYmo4mSZg==", "dependencies": { - "cron": "2.4.1", - "uuid": "9.0.0" + "cron": "3.1.7", + "uuid": "9.0.1" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", - "reflect-metadata": "^0.1.12" + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" } }, "node_modules/@nestjs/schematics": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.2.tgz", - "integrity": "sha512-DaZZjymYoIfRqC5W62lnYXIIods1PDY6CGc8+IpRwyinzffjKxZ3DF3exu+mdyvllzkXo9DTXkoX4zOPSJHCkw==", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.1.tgz", + "integrity": "sha512-o4lfCnEeIkfJhGBbLZxTuVWcGuqDCFwg5OrvpgRUBM7vI/vONvKKiB5riVNpO+JqXoH0I42NNeDb0m4V5RREig==", "dependencies": { - "@angular-devkit/core": "16.1.8", - "@angular-devkit/schematics": "16.1.8", + "@angular-devkit/core": "17.1.2", + "@angular-devkit/schematics": "17.1.2", "comment-json": "4.2.3", - "jsonc-parser": "3.2.0", + "jsonc-parser": "3.2.1", "pluralize": "8.0.0" }, "peerDependencies": { "typescript": ">=4.8.2" } }, - "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.1.8.tgz", - "integrity": "sha512-dSRD/+bGanArIXkj+kaU1kDFleZeQMzmBiOXX+pK0Ah9/0Yn1VmY3RZh1zcX9vgIQXV+t7UPrTpOjaERMUtVGw==", - "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.0", - "rxjs": "7.8.1", - "source-map": "0.7.4" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "chokidar": "^3.5.2" - }, - "peerDependenciesMeta": { - "chokidar": { - "optional": true - } - } - }, - "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { - "version": "16.1.8", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.1.8.tgz", - "integrity": "sha512-6LyzMdFJs337RTxxkI2U1Ndw0CW5mMX/aXWl8d7cW2odiSrAg8IdlMqpc+AM8+CPfsB0FtS1aWkEZqJLT0jHOg==", - "dependencies": { - "@angular-devkit/core": "16.1.8", - "jsonc-parser": "3.2.0", - "magic-string": "0.30.0", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^16.14.0 || >=18.10.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, - "node_modules/@nestjs/schematics/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@nestjs/schematics/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==" - }, - "node_modules/@nestjs/schematics/node_modules/magic-string": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@nestjs/schematics/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "engines": { - "node": ">= 8" - } + "node_modules/@nestjs/schematics/node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==" }, "node_modules/@nestjs/swagger": { - "version": "7.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.1.8.tgz", - "integrity": "sha512-Jpl3laGAqvyWccc3auLU0mMjl5hJ2kqzzDb63ynJi5NMbFlgBwrR8FCGBVstSsqL9YSJWLR4L1BZzVmVExcY+g==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.3.1.tgz", + "integrity": "sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw==", "dependencies": { - "@nestjs/mapped-types": "2.0.2", + "@microsoft/tsdoc": "^0.14.2", + "@nestjs/mapped-types": "2.0.5", "js-yaml": "4.1.0", "lodash": "4.17.21", "path-to-regexp": "3.2.0", - "swagger-ui-dist": "5.3.1" + "swagger-ui-dist": "5.11.2" }, "peerDependencies": { - "@fastify/static": "^6.0.0", + "@fastify/static": "^6.0.0 || ^7.0.0", "@nestjs/common": "^9.0.0 || ^10.0.0", "@nestjs/core": "^9.0.0 || ^10.0.0", "class-transformer": "*", "class-validator": "*", - "reflect-metadata": "^0.1.12" + "reflect-metadata": "^0.1.12 || ^0.2.0" }, "peerDependenciesMeta": { "@fastify/static": { @@ -2231,20 +3212,22 @@ } }, "node_modules/@nestjs/testing": { - "version": "9.4.3", - "license": "MIT", + "version": "10.3.8", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.8.tgz", + "integrity": "sha512-hpX9das2TdFTKQ4/2ojhjI6YgXtCfXRKui3A4Qaj54VVzc5+mtK502Jj18Vzji98o9MVS6skmYu+S/UvW3U6Fw==", + "dev": true, "dependencies": { - "tslib": "2.5.3" + "tslib": "2.6.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/core": "^9.0.0", - "@nestjs/microservices": "^9.0.0", - "@nestjs/platform-express": "^9.0.0" + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0" }, "peerDependenciesMeta": { "@nestjs/microservices": { @@ -2255,40 +3238,21 @@ } } }, - "node_modules/@nestjs/typeorm": { - "version": "9.0.1", - "license": "MIT", - "dependencies": { - "uuid": "8.3.2" - }, - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0", - "reflect-metadata": "^0.1.13", - "rxjs": "^7.2.0", - "typeorm": "^0.3.0" - } - }, - "node_modules/@nestjs/typeorm/node_modules/uuid": { - "version": "8.3.2", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@noble/curves": { - "version": "1.1.0", - "license": "MIT", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", + "integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==", "dependencies": { - "@noble/hashes": "1.3.1" + "@noble/hashes": "1.4.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@noble/hashes": { - "version": "1.3.1", - "license": "MIT", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "engines": { "node": ">= 16" }, @@ -2298,8 +3262,9 @@ }, "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", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2310,16 +3275,18 @@ }, "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", "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", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -2330,7 +3297,8 @@ }, "node_modules/@nuxtjs/opencollective": { "version": "0.3.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", "dependencies": { "chalk": "^4.1.0", "consola": "^2.15.0", @@ -2344,543 +3312,569 @@ "npm": ">=5.0.0" } }, - "node_modules/@nuxtjs/opencollective/node_modules/node-fetch": { - "version": "2.6.11", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.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==", + "optional": true, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=14" + } + }, + "node_modules/@polkadot-api/client": { + "version": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/client/-/client-0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0.tgz", + "integrity": "sha512-0fqK6pUKcGHSG2pBvY+gfSS+1mMdjd/qRygAcKI5d05tKsnZLRnmhb9laDguKmGEIB0Bz9vQqNK3gIN/cfvVwg==", + "optional": true, + "dependencies": { + "@polkadot-api/metadata-builders": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@polkadot-api/substrate-bindings": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@polkadot-api/substrate-client": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@polkadot-api/utils": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0" }, "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "rxjs": ">=7.8.0" } }, - "node_modules/@pkgr/utils": { - "version": "2.4.1", - "dev": true, - "license": "MIT", + "node_modules/@polkadot-api/json-rpc-provider": { + "version": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider/-/json-rpc-provider-0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0.tgz", + "integrity": "sha512-EaUS9Fc3wsiUr6ZS43PQqaRScW7kM6DYbuM/ou0aYjm8N9MBqgDbGm2oL6RE1vAVmOfEuHcXZuZkhzWtyvQUtA==", + "optional": true + }, + "node_modules/@polkadot-api/json-rpc-provider-proxy": { + "version": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/json-rpc-provider-proxy/-/json-rpc-provider-proxy-0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0.tgz", + "integrity": "sha512-0hZ8vtjcsyCX8AyqP2sqUHa1TFFfxGWmlXJkit0Nqp9b32MwZqn5eaUAiV2rNuEpoglKOdKnkGtUF8t5MoodKw==", + "optional": true + }, + "node_modules/@polkadot-api/metadata-builders": { + "version": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/metadata-builders/-/metadata-builders-0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0.tgz", + "integrity": "sha512-BD7rruxChL1VXt0icC2gD45OtT9ofJlql0qIllHSRYgama1CR2Owt+ApInQxB+lWqM+xNOznZRpj8CXNDvKIMg==", + "optional": true, "dependencies": { - "cross-spawn": "^7.0.3", - "fast-glob": "^3.2.12", - "is-glob": "^4.0.3", - "open": "^9.1.0", - "picocolors": "^1.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" + "@polkadot-api/substrate-bindings": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@polkadot-api/utils": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0" } }, - "node_modules/@polkadot/api": { - "version": "10.9.1", - "license": "Apache-2.0", + "node_modules/@polkadot-api/substrate-bindings": { + "version": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-bindings/-/substrate-bindings-0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0.tgz", + "integrity": "sha512-N4vdrZopbsw8k57uG58ofO7nLXM4Ai7835XqakN27MkjXMp5H830A1KJE0L9sGQR7ukOCDEIHHcwXVrzmJ/PBg==", + "optional": true, "dependencies": { - "@polkadot/api-augment": "10.9.1", - "@polkadot/api-base": "10.9.1", - "@polkadot/api-derive": "10.9.1", - "@polkadot/keyring": "^12.3.1", - "@polkadot/rpc-augment": "10.9.1", - "@polkadot/rpc-core": "10.9.1", - "@polkadot/rpc-provider": "10.9.1", - "@polkadot/types": "10.9.1", - "@polkadot/types-augment": "10.9.1", - "@polkadot/types-codec": "10.9.1", - "@polkadot/types-create": "10.9.1", - "@polkadot/types-known": "10.9.1", - "@polkadot/util": "^12.3.1", - "@polkadot/util-crypto": "^12.3.1", + "@noble/hashes": "^1.3.1", + "@polkadot-api/utils": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@scure/base": "^1.1.1", + "scale-ts": "^1.6.0" + } + }, + "node_modules/@polkadot-api/substrate-client": { + "version": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/substrate-client/-/substrate-client-0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0.tgz", + "integrity": "sha512-lcdvd2ssUmB1CPzF8s2dnNOqbrDa+nxaaGbuts+Vo8yjgSKwds2Lo7Oq+imZN4VKW7t9+uaVcKFLMF7PdH0RWw==", + "optional": true + }, + "node_modules/@polkadot-api/utils": { + "version": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "resolved": "https://registry.npmjs.org/@polkadot-api/utils/-/utils-0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0.tgz", + "integrity": "sha512-0CYaCjfLQJTCRCiYvZ81OncHXEKPzAexCMoVloR+v2nl/O2JRya/361MtPkeNLC6XBoaEgLAG9pWQpH3WePzsw==", + "optional": true + }, + "node_modules/@polkadot/api": { + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-10.13.1.tgz", + "integrity": "sha512-YrKWR4TQR5CDyGkF0mloEUo7OsUA+bdtENpJGOtNavzOQUDEbxFE0PVzokzZfVfHhHX2CojPVmtzmmLxztyJkg==", + "dependencies": { + "@polkadot/api-augment": "10.13.1", + "@polkadot/api-base": "10.13.1", + "@polkadot/api-derive": "10.13.1", + "@polkadot/keyring": "^12.6.2", + "@polkadot/rpc-augment": "10.13.1", + "@polkadot/rpc-core": "10.13.1", + "@polkadot/rpc-provider": "10.13.1", + "@polkadot/types": "10.13.1", + "@polkadot/types-augment": "10.13.1", + "@polkadot/types-codec": "10.13.1", + "@polkadot/types-create": "10.13.1", + "@polkadot/types-known": "10.13.1", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", "eventemitter3": "^5.0.1", "rxjs": "^7.8.1", - "tslib": "^2.5.3" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/api-augment": { - "version": "10.9.1", - "license": "Apache-2.0", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-augment/-/api-augment-10.13.1.tgz", + "integrity": "sha512-IAKaCp19QxgOG4HKk9RAgUgC/VNVqymZ2GXfMNOZWImZhxRIbrK+raH5vN2MbWwtVHpjxyXvGsd1RRhnohI33A==", "dependencies": { - "@polkadot/api-base": "10.9.1", - "@polkadot/rpc-augment": "10.9.1", - "@polkadot/types": "10.9.1", - "@polkadot/types-augment": "10.9.1", - "@polkadot/types-codec": "10.9.1", - "@polkadot/util": "^12.3.1", - "tslib": "^2.5.3" + "@polkadot/api-base": "10.13.1", + "@polkadot/rpc-augment": "10.13.1", + "@polkadot/types": "10.13.1", + "@polkadot/types-augment": "10.13.1", + "@polkadot/types-codec": "10.13.1", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/api-base": { - "version": "10.9.1", - "license": "Apache-2.0", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-base/-/api-base-10.13.1.tgz", + "integrity": "sha512-Okrw5hjtEjqSMOG08J6qqEwlUQujTVClvY1/eZkzKwNzPelWrtV6vqfyJklB7zVhenlxfxqhZKKcY7zWSW/q5Q==", "dependencies": { - "@polkadot/rpc-core": "10.9.1", - "@polkadot/types": "10.9.1", - "@polkadot/util": "^12.3.1", + "@polkadot/rpc-core": "10.13.1", + "@polkadot/types": "10.13.1", + "@polkadot/util": "^12.6.2", "rxjs": "^7.8.1", - "tslib": "^2.5.3" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/api-derive": { - "version": "10.9.1", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/api": "10.9.1", - "@polkadot/api-augment": "10.9.1", - "@polkadot/api-base": "10.9.1", - "@polkadot/rpc-core": "10.9.1", - "@polkadot/types": "10.9.1", - "@polkadot/types-codec": "10.9.1", - "@polkadot/util": "^12.3.1", - "@polkadot/util-crypto": "^12.3.1", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-10.13.1.tgz", + "integrity": "sha512-ef0H0GeCZ4q5Om+c61eLLLL29UxFC2/u/k8V1K2JOIU+2wD5LF7sjAoV09CBMKKHfkLenRckVk2ukm4rBqFRpg==", + "dependencies": { + "@polkadot/api": "10.13.1", + "@polkadot/api-augment": "10.13.1", + "@polkadot/api-base": "10.13.1", + "@polkadot/rpc-core": "10.13.1", + "@polkadot/types": "10.13.1", + "@polkadot/types-codec": "10.13.1", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", "rxjs": "^7.8.1", - "tslib": "^2.5.3" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/keyring": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-12.6.2.tgz", + "integrity": "sha512-O3Q7GVmRYm8q7HuB3S0+Yf/q/EB2egKRRU3fv9b3B7V+A52tKzA+vIwEmNVaD1g5FKW9oB97rmpggs0zaKFqHw==", "dependencies": { - "@polkadot/util": "12.3.2", - "@polkadot/util-crypto": "12.3.2", - "tslib": "^2.5.3" + "@polkadot/util": "12.6.2", + "@polkadot/util-crypto": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { - "@polkadot/util": "12.3.2", - "@polkadot/util-crypto": "12.3.2" + "@polkadot/util": "12.6.2", + "@polkadot/util-crypto": "12.6.2" } }, "node_modules/@polkadot/networks": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-12.6.2.tgz", + "integrity": "sha512-1oWtZm1IvPWqvMrldVH6NI2gBoCndl5GEwx7lAuQWGr7eNL+6Bdc5K3Z9T0MzFvDGoi2/CBqjX9dRKo39pDC/w==", "dependencies": { - "@polkadot/util": "12.3.2", - "@substrate/ss58-registry": "^1.40.0", - "tslib": "^2.5.3" + "@polkadot/util": "12.6.2", + "@substrate/ss58-registry": "^1.44.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/rpc-augment": { - "version": "10.9.1", - "license": "Apache-2.0", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-augment/-/rpc-augment-10.13.1.tgz", + "integrity": "sha512-iLsWUW4Jcx3DOdVrSHtN0biwxlHuTs4QN2hjJV0gd0jo7W08SXhWabZIf9mDmvUJIbR7Vk+9amzvegjRyIf5+A==", "dependencies": { - "@polkadot/rpc-core": "10.9.1", - "@polkadot/types": "10.9.1", - "@polkadot/types-codec": "10.9.1", - "@polkadot/util": "^12.3.1", - "tslib": "^2.5.3" + "@polkadot/rpc-core": "10.13.1", + "@polkadot/types": "10.13.1", + "@polkadot/types-codec": "10.13.1", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/rpc-core": { - "version": "10.9.1", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/rpc-augment": "10.9.1", - "@polkadot/rpc-provider": "10.9.1", - "@polkadot/types": "10.9.1", - "@polkadot/util": "^12.3.1", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-10.13.1.tgz", + "integrity": "sha512-eoejSHa+/tzHm0vwic62/aptTGbph8vaBpbvLIK7gd00+rT813ROz5ckB1CqQBFB23nHRLuzzX/toY8ID3xrKw==", + "dependencies": { + "@polkadot/rpc-augment": "10.13.1", + "@polkadot/rpc-provider": "10.13.1", + "@polkadot/types": "10.13.1", + "@polkadot/util": "^12.6.2", "rxjs": "^7.8.1", - "tslib": "^2.5.3" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/rpc-provider": { - "version": "10.9.1", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/keyring": "^12.3.1", - "@polkadot/types": "10.9.1", - "@polkadot/types-support": "10.9.1", - "@polkadot/util": "^12.3.1", - "@polkadot/util-crypto": "^12.3.1", - "@polkadot/x-fetch": "^12.3.1", - "@polkadot/x-global": "^12.3.1", - "@polkadot/x-ws": "^12.3.1", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-10.13.1.tgz", + "integrity": "sha512-oJ7tatVXYJ0L7NpNiGd69D558HG5y5ZDmH2Bp9Dd4kFTQIiV8A39SlWwWUPCjSsen9lqSvvprNLnG/VHTpenbw==", + "dependencies": { + "@polkadot/keyring": "^12.6.2", + "@polkadot/types": "10.13.1", + "@polkadot/types-support": "10.13.1", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", + "@polkadot/x-fetch": "^12.6.2", + "@polkadot/x-global": "^12.6.2", + "@polkadot/x-ws": "^12.6.2", "eventemitter3": "^5.0.1", - "mock-socket": "^9.2.1", - "nock": "^13.3.1", - "tslib": "^2.5.3" + "mock-socket": "^9.3.1", + "nock": "^13.5.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "optionalDependencies": { - "@substrate/connect": "0.7.26" - } - }, - "node_modules/@polkadot/typegen": { - "version": "10.9.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@polkadot/api": "10.9.1", - "@polkadot/api-augment": "10.9.1", - "@polkadot/rpc-augment": "10.9.1", - "@polkadot/rpc-provider": "10.9.1", - "@polkadot/types": "10.9.1", - "@polkadot/types-augment": "10.9.1", - "@polkadot/types-codec": "10.9.1", - "@polkadot/types-create": "10.9.1", - "@polkadot/types-support": "10.9.1", - "@polkadot/util": "^12.3.1", - "@polkadot/util-crypto": "^12.3.1", - "@polkadot/x-ws": "^12.3.1", - "handlebars": "^4.7.7", - "tslib": "^2.5.3", - "yargs": "^17.7.2" - }, - "bin": { - "polkadot-types-chain-info": "scripts/polkadot-types-chain-info.mjs", - "polkadot-types-from-chain": "scripts/polkadot-types-from-chain.mjs", - "polkadot-types-from-defs": "scripts/polkadot-types-from-defs.mjs", - "polkadot-types-internal-interfaces": "scripts/polkadot-types-internal-interfaces.mjs", - "polkadot-types-internal-metadata": "scripts/polkadot-types-internal-metadata.mjs" - }, - "engines": { - "node": ">=16" + "@substrate/connect": "0.8.8" } }, "node_modules/@polkadot/types": { - "version": "10.9.1", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/keyring": "^12.3.1", - "@polkadot/types-augment": "10.9.1", - "@polkadot/types-codec": "10.9.1", - "@polkadot/types-create": "10.9.1", - "@polkadot/util": "^12.3.1", - "@polkadot/util-crypto": "^12.3.1", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-10.13.1.tgz", + "integrity": "sha512-Hfvg1ZgJlYyzGSAVrDIpp3vullgxrjOlh/CSThd/PI4TTN1qHoPSFm2hs77k3mKkOzg+LrWsLE0P/LP2XddYcw==", + "dependencies": { + "@polkadot/keyring": "^12.6.2", + "@polkadot/types-augment": "10.13.1", + "@polkadot/types-codec": "10.13.1", + "@polkadot/types-create": "10.13.1", + "@polkadot/util": "^12.6.2", + "@polkadot/util-crypto": "^12.6.2", "rxjs": "^7.8.1", - "tslib": "^2.5.3" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/types-augment": { - "version": "10.9.1", - "license": "Apache-2.0", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-augment/-/types-augment-10.13.1.tgz", + "integrity": "sha512-TcrLhf95FNFin61qmVgOgayzQB/RqVsSg9thAso1Fh6pX4HSbvI35aGPBAn3SkA6R+9/TmtECirpSNLtIGFn0g==", "dependencies": { - "@polkadot/types": "10.9.1", - "@polkadot/types-codec": "10.9.1", - "@polkadot/util": "^12.3.1", - "tslib": "^2.5.3" + "@polkadot/types": "10.13.1", + "@polkadot/types-codec": "10.13.1", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/types-codec": { - "version": "10.9.1", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/util": "^12.3.1", - "@polkadot/x-bigint": "^12.3.1", - "tslib": "^2.5.3" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/types-codec/node_modules/@polkadot/x-bigint": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-codec/-/types-codec-10.13.1.tgz", + "integrity": "sha512-AiQ2Vv2lbZVxEdRCN8XSERiWlOWa2cTDLnpAId78EnCtx4HLKYQSd+Jk9Y4BgO35R79mchK4iG+w6gZ+ukG2bg==", "dependencies": { - "@polkadot/x-global": "12.3.2", - "tslib": "^2.5.3" + "@polkadot/util": "^12.6.2", + "@polkadot/x-bigint": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/types-create": { - "version": "10.9.1", - "license": "Apache-2.0", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-create/-/types-create-10.13.1.tgz", + "integrity": "sha512-Usn1jqrz35SXgCDAqSXy7mnD6j4RvB4wyzTAZipFA6DGmhwyxxIgOzlWQWDb+1PtPKo9vtMzen5IJ+7w5chIeA==", "dependencies": { - "@polkadot/types-codec": "10.9.1", - "@polkadot/util": "^12.3.1", - "tslib": "^2.5.3" + "@polkadot/types-codec": "10.13.1", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/types-known": { - "version": "10.9.1", - "license": "Apache-2.0", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-10.13.1.tgz", + "integrity": "sha512-uHjDW05EavOT5JeU8RbiFWTgPilZ+odsCcuEYIJGmK+es3lk/Qsdns9Zb7U7NJl7eJ6OWmRtyrWsLs+bU+jjIQ==", "dependencies": { - "@polkadot/networks": "^12.3.1", - "@polkadot/types": "10.9.1", - "@polkadot/types-codec": "10.9.1", - "@polkadot/types-create": "10.9.1", - "@polkadot/util": "^12.3.1", - "tslib": "^2.5.3" + "@polkadot/networks": "^12.6.2", + "@polkadot/types": "10.13.1", + "@polkadot/types-codec": "10.13.1", + "@polkadot/types-create": "10.13.1", + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/types-support": { - "version": "10.9.1", - "license": "Apache-2.0", + "version": "10.13.1", + "resolved": "https://registry.npmjs.org/@polkadot/types-support/-/types-support-10.13.1.tgz", + "integrity": "sha512-4gEPfz36XRQIY7inKq0HXNVVhR6HvXtm7yrEmuBuhM86LE0lQQBkISUSgR358bdn2OFSLMxMoRNoh3kcDvdGDQ==", "dependencies": { - "@polkadot/util": "^12.3.1", - "tslib": "^2.5.3" + "@polkadot/util": "^12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/util": { - "version": "12.3.2", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-bigint": "12.3.2", - "@polkadot/x-global": "12.3.2", - "@polkadot/x-textdecoder": "12.3.2", - "@polkadot/x-textencoder": "12.3.2", - "@types/bn.js": "^5.1.1", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-12.6.2.tgz", + "integrity": "sha512-l8TubR7CLEY47240uki0TQzFvtnxFIO7uI/0GoWzpYD/O62EIAMRsuY01N4DuwgKq2ZWD59WhzsLYmA5K6ksdw==", + "dependencies": { + "@polkadot/x-bigint": "12.6.2", + "@polkadot/x-global": "12.6.2", + "@polkadot/x-textdecoder": "12.6.2", + "@polkadot/x-textencoder": "12.6.2", + "@types/bn.js": "^5.1.5", "bn.js": "^5.2.1", - "tslib": "^2.5.3" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/util-crypto": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-12.6.2.tgz", + "integrity": "sha512-FEWI/dJ7wDMNN1WOzZAjQoIcCP/3vz3wvAp5QQm+lOrzOLj0iDmaIGIcBkz8HVm3ErfSe/uKP0KS4jgV/ib+Mg==", "dependencies": { - "@noble/curves": "1.1.0", - "@noble/hashes": "1.3.1", - "@polkadot/networks": "12.3.2", - "@polkadot/util": "12.3.2", - "@polkadot/wasm-crypto": "^7.2.1", - "@polkadot/wasm-util": "^7.2.1", - "@polkadot/x-bigint": "12.3.2", - "@polkadot/x-randomvalues": "12.3.2", - "@scure/base": "1.1.1", - "tslib": "^2.5.3" + "@noble/curves": "^1.3.0", + "@noble/hashes": "^1.3.3", + "@polkadot/networks": "12.6.2", + "@polkadot/util": "12.6.2", + "@polkadot/wasm-crypto": "^7.3.2", + "@polkadot/wasm-util": "^7.3.2", + "@polkadot/x-bigint": "12.6.2", + "@polkadot/x-randomvalues": "12.6.2", + "@scure/base": "^1.1.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { - "@polkadot/util": "12.3.2" + "@polkadot/util": "12.6.2" } }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/wasm-bridge": { - "version": "7.2.1", - "license": "Apache-2.0", + "node_modules/@polkadot/wasm-bridge": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-bridge/-/wasm-bridge-7.3.2.tgz", + "integrity": "sha512-AJEXChcf/nKXd5Q/YLEV5dXQMle3UNT7jcXYmIffZAo/KI394a+/24PaISyQjoNC0fkzS1Q8T5pnGGHmXiVz2g==", "dependencies": { - "@polkadot/wasm-util": "7.2.1", - "tslib": "^2.5.0" + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/wasm-crypto": { - "version": "7.2.1", - "license": "Apache-2.0", + "node_modules/@polkadot/wasm-crypto": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-7.3.2.tgz", + "integrity": "sha512-+neIDLSJ6jjVXsjyZ5oLSv16oIpwp+PxFqTUaZdZDoA2EyFRQB8pP7+qLsMNk+WJuhuJ4qXil/7XiOnZYZ+wxw==", "dependencies": { - "@polkadot/wasm-bridge": "7.2.1", - "@polkadot/wasm-crypto-asmjs": "7.2.1", - "@polkadot/wasm-crypto-init": "7.2.1", - "@polkadot/wasm-crypto-wasm": "7.2.1", - "@polkadot/wasm-util": "7.2.1", - "tslib": "^2.5.0" + "@polkadot/wasm-bridge": "7.3.2", + "@polkadot/wasm-crypto-asmjs": "7.3.2", + "@polkadot/wasm-crypto-init": "7.3.2", + "@polkadot/wasm-crypto-wasm": "7.3.2", + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { "@polkadot/util": "*", "@polkadot/x-randomvalues": "*" } }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/wasm-crypto-init": { - "version": "7.2.1", - "license": "Apache-2.0", + "node_modules/@polkadot/wasm-crypto-asmjs": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-7.3.2.tgz", + "integrity": "sha512-QP5eiUqUFur/2UoF2KKKYJcesc71fXhQFLT3D4ZjG28Mfk2ZPI0QNRUfpcxVQmIUpV5USHg4geCBNuCYsMm20Q==", "dependencies": { - "@polkadot/wasm-bridge": "7.2.1", - "@polkadot/wasm-crypto-asmjs": "7.2.1", - "@polkadot/wasm-crypto-wasm": "7.2.1", - "@polkadot/wasm-util": "7.2.1", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { - "@polkadot/util": "*", - "@polkadot/x-randomvalues": "*" + "@polkadot/util": "*" } }, - "node_modules/@polkadot/util-crypto/node_modules/@polkadot/x-bigint": { - "version": "12.3.2", - "license": "Apache-2.0", + "node_modules/@polkadot/wasm-crypto-init": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-init/-/wasm-crypto-init-7.3.2.tgz", + "integrity": "sha512-FPq73zGmvZtnuJaFV44brze3Lkrki3b4PebxCy9Fplw8nTmisKo9Xxtfew08r0njyYh+uiJRAxPCXadkC9sc8g==", "dependencies": { - "@polkadot/x-global": "12.3.2", - "tslib": "^2.5.3" + "@polkadot/wasm-bridge": "7.3.2", + "@polkadot/wasm-crypto-asmjs": "7.3.2", + "@polkadot/wasm-crypto-wasm": "7.3.2", + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" - } - }, - "node_modules/@polkadot/util/node_modules/@polkadot/x-bigint": { - "version": "12.3.2", - "license": "Apache-2.0", - "dependencies": { - "@polkadot/x-global": "12.3.2", - "tslib": "^2.5.3" + "node": ">=18" }, - "engines": { - "node": ">=16" + "peerDependencies": { + "@polkadot/util": "*", + "@polkadot/x-randomvalues": "*" } }, - "node_modules/@polkadot/wasm-crypto-asmjs": { - "version": "7.2.1", - "license": "Apache-2.0", + "node_modules/@polkadot/wasm-crypto-wasm": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-7.3.2.tgz", + "integrity": "sha512-15wd0EMv9IXs5Abp1ZKpKKAVyZPhATIAHfKsyoWCEFDLSOA0/K0QGOxzrAlsrdUkiKZOq7uzSIgIDgW8okx2Mw==", "dependencies": { - "tslib": "^2.5.0" + "@polkadot/wasm-util": "7.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { "@polkadot/util": "*" } }, - "node_modules/@polkadot/wasm-crypto-wasm": { - "version": "7.2.1", - "license": "Apache-2.0", + "node_modules/@polkadot/wasm-util": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-util/-/wasm-util-7.3.2.tgz", + "integrity": "sha512-bmD+Dxo1lTZyZNxbyPE380wd82QsX+43mgCm40boyKrRppXEyQmWT98v/Poc7chLuskYb6X8IQ6lvvK2bGR4Tg==", "dependencies": { - "@polkadot/wasm-util": "7.2.1", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { "@polkadot/util": "*" } }, - "node_modules/@polkadot/wasm-util": { - "version": "7.2.1", - "license": "Apache-2.0", + "node_modules/@polkadot/x-bigint": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-bigint/-/x-bigint-12.6.2.tgz", + "integrity": "sha512-HSIk60uFPX4GOFZSnIF7VYJz7WZA7tpFJsne7SzxOooRwMTWEtw3fUpFy5cYYOeLh17/kHH1Y7SVcuxzVLc74Q==", "dependencies": { - "tslib": "^2.5.0" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" - }, - "peerDependencies": { - "@polkadot/util": "*" + "node": ">=18" } }, "node_modules/@polkadot/x-fetch": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-12.6.2.tgz", + "integrity": "sha512-8wM/Z9JJPWN1pzSpU7XxTI1ldj/AfC8hKioBlUahZ8gUiJaOF7K9XEFCrCDLis/A1BoOu7Ne6WMx/vsJJIbDWw==", "dependencies": { - "@polkadot/x-global": "12.3.2", - "node-fetch": "^3.3.1", - "tslib": "^2.5.3" + "@polkadot/x-global": "12.6.2", + "node-fetch": "^3.3.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, - "node_modules/@polkadot/x-global": { - "version": "12.3.2", - "license": "Apache-2.0", + "node_modules/@polkadot/x-fetch/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dependencies": { - "tslib": "^2.5.3" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": ">=16" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/@polkadot/x-global": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-12.6.2.tgz", + "integrity": "sha512-a8d6m+PW98jmsYDtAWp88qS4dl8DyqUBsd0S+WgyfSMtpEXu6v9nXDgPZgwF5xdDvXhm+P0ZfVkVTnIGrScb5g==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" } }, "node_modules/@polkadot/x-randomvalues": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-12.6.2.tgz", + "integrity": "sha512-Vr8uG7rH2IcNJwtyf5ebdODMcr0XjoCpUbI91Zv6AlKVYOGKZlKLYJHIwpTaKKB+7KPWyQrk4Mlym/rS7v9feg==", "dependencies": { - "@polkadot/x-global": "12.3.2", - "tslib": "^2.5.3" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "peerDependencies": { - "@polkadot/util": "12.3.2", + "@polkadot/util": "12.6.2", "@polkadot/wasm-util": "*" } }, "node_modules/@polkadot/x-textdecoder": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-12.6.2.tgz", + "integrity": "sha512-M1Bir7tYvNappfpFWXOJcnxUhBUFWkUFIdJSyH0zs5LmFtFdbKAeiDXxSp2Swp5ddOZdZgPac294/o2TnQKN1w==", "dependencies": { - "@polkadot/x-global": "12.3.2", - "tslib": "^2.5.3" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/x-textencoder": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-12.6.2.tgz", + "integrity": "sha512-4N+3UVCpI489tUJ6cv3uf0PjOHvgGp9Dl+SZRLgFGt9mvxnvpW/7+XBADRMtlG4xi5gaRK7bgl5bmY6OMDsNdw==", "dependencies": { - "@polkadot/x-global": "12.3.2", - "tslib": "^2.5.3" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@polkadot/x-ws": { - "version": "12.3.2", - "license": "Apache-2.0", + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-12.6.2.tgz", + "integrity": "sha512-cGZWo7K5eRRQCRl2LrcyCYsrc3lRbTlixZh3AzgU8uX4wASVGRlNWi/Hf4TtHNe1ExCDmxabJzdIsABIfrr7xw==", "dependencies": { - "@polkadot/x-global": "12.3.2", - "tslib": "^2.5.3", - "ws": "^8.13.0" + "@polkadot/x-global": "12.6.2", + "tslib": "^2.6.2", + "ws": "^8.15.1" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/@protobufjs/aspromise": { @@ -2937,98 +3931,43 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, - "node_modules/@redis/bloom": { - "version": "1.2.0", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/client": { - "version": "1.5.8", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cluster-key-slot": "1.1.2", - "generic-pool": "3.9.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@redis/graph": { - "version": "1.1.0", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/json": { - "version": "1.0.4", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/search": { - "version": "1.1.3", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, - "node_modules/@redis/time-series": { - "version": "1.0.4", - "license": "MIT", - "optional": true, - "peer": true, - "peerDependencies": { - "@redis/client": "^1.0.0" - } - }, "node_modules/@scure/base": { - "version": "1.1.1", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", + "integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==", + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@sideway/address": { - "version": "4.1.4", - "license": "BSD-3-Clause", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", "dependencies": { "@hapi/hoek": "^9.0.0" } }, "node_modules/@sideway/formula": { "version": "3.0.1", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" }, "node_modules/@sideway/pinpoint": { "version": "2.0.0", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "license": "MIT" + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true }, "node_modules/@sindresorhus/is": { - "version": "5.4.1", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", "dev": true, - "license": "MIT", "engines": { "node": ">=14.16" }, @@ -3037,2550 +3976,2221 @@ } }, "node_modules/@sinonjs/commons": { - "version": "3.0.0", - "license": "BSD-3-Clause", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.1.0", - "license": "BSD-3-Clause", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@sqltools/formatter": { - "version": "1.2.5", - "license": "MIT", - "peer": true - }, - "node_modules/@substrate/connect": { - "version": "0.7.26", - "license": "GPL-3.0-only", - "optional": true, + "node_modules/@smithy/abort-controller": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.2.0.tgz", + "integrity": "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==", "dependencies": { - "@substrate/connect-extension-protocol": "^1.0.1", - "eventemitter3": "^4.0.7", - "smoldot": "1.0.4" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@substrate/connect-extension-protocol": { - "version": "1.0.1", - "license": "GPL-3.0-only", - "optional": true - }, - "node_modules/@substrate/connect/node_modules/eventemitter3": { - "version": "4.0.7", - "license": "MIT", - "optional": true + "node_modules/@smithy/chunked-blob-reader": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.2.0.tgz", + "integrity": "sha512-3GJNvRwXBGdkDZZOGiziVYzDpn4j6zfyULHMDKAGIUo72yHALpE9CbhfQp/XcLNVoc1byfMpn6uW5H2BqPjgaQ==", + "dependencies": { + "tslib": "^2.6.2" + } }, - "node_modules/@substrate/ss58-registry": { - "version": "1.40.0", - "license": "Apache-2.0" + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.2.0.tgz", + "integrity": "sha512-VNB5+1oCgX3Fzs072yuRsUoC2N4Zg/LJ11DTxX3+Qu+Paa6AmbIF0E9sc2wthz9Psrk/zcOlTCyuposlIhPjZQ==", + "dependencies": { + "@smithy/util-base64": "^2.3.0", + "tslib": "^2.6.2" + } }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/config-resolver": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.2.0.tgz", + "integrity": "sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==", "dependencies": { - "defer-to-connect": "^2.0.1" + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-config-provider": "^2.3.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.16" + "node": ">=14.0.0" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "license": "MIT" + "node_modules/@smithy/core": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.4.2.tgz", + "integrity": "sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA==", + "dependencies": { + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-retry": "^2.3.1", + "@smithy/middleware-serde": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@types/babel__core": { - "version": "7.20.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/credential-provider-imds": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.3.0.tgz", + "integrity": "sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@smithy/node-config-provider": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-codec": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.2.0.tgz", + "integrity": "sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw==", "dependencies": { - "@babel/types": "^7.0.0" + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^2.12.0", + "@smithy/util-hex-encoding": "^2.2.0", + "tslib": "^2.6.2" } }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "dev": true, - "license": "MIT", + "node_modules/@smithy/eventstream-serde-browser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.2.0.tgz", + "integrity": "sha512-UaPf8jKbcP71BGiO0CdeLmlg+RhWnlN8ipsMSdwvqBFigl5nil3rHOI/5GE3tfiuX8LvY5Z9N0meuU7Rab7jWw==", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@smithy/eventstream-serde-universal": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/babel__traverse": { - "version": "7.20.1", - "license": "MIT", + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.2.0.tgz", + "integrity": "sha512-RHhbTw/JW3+r8QQH7PrganjNCiuiEZmpi6fYUAetFfPLfZ6EkiA08uN3EFfcyKubXQxOwTeJRZSQmDDCdUshaA==", "dependencies": { - "@babel/types": "^7.20.7" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/bn.js": { - "version": "5.1.1", - "license": "MIT", + "node_modules/@smithy/eventstream-serde-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.2.0.tgz", + "integrity": "sha512-zpQMtJVqCUMn+pCSFcl9K/RPNtQE0NuMh8sKpCdEHafhwRsjP50Oq/4kMmvxSRy6d8Jslqd8BLvDngrUtmN9iA==", "dependencies": { - "@types/node": "*" + "@smithy/eventstream-serde-universal": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "node_modules/@smithy/eventstream-serde-universal": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.2.0.tgz", + "integrity": "sha512-pvoe/vvJY0mOpuF84BEtyZoYfbehiFj8KKWk1ds2AT0mTLYFVs+7sBJZmioOFdBXKd48lfrx1vumdPdmGlCLxA==", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "@smithy/eventstream-codec": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "node_modules/@smithy/fetch-http-handler": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.5.0.tgz", + "integrity": "sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==", "dependencies": { - "@types/node": "*" + "@smithy/protocol-http": "^3.3.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/util-base64": "^2.3.0", + "tslib": "^2.6.2" } }, - "node_modules/@types/cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", - "dev": true + "node_modules/@smithy/hash-blob-browser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-2.2.0.tgz", + "integrity": "sha512-SGPoVH8mdXBqrkVCJ1Hd1X7vh1zDXojNN1yZyZTZsCno99hVue9+IYzWDjq/EQDDXxmITB0gBmuyPh8oAZSTcg==", + "dependencies": { + "@smithy/chunked-blob-reader": "^2.2.0", + "@smithy/chunked-blob-reader-native": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + } }, - "node_modules/@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "node_modules/@smithy/hash-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.2.0.tgz", + "integrity": "sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==", "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" + "@smithy/types": "^2.12.0", + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "node_modules/@smithy/hash-stream-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-2.2.0.tgz", + "integrity": "sha512-aT+HCATOSRMGpPI7bi7NSsTNVZE/La9IaxLXWoVAYMxHT5hGO3ZOGEMZQg8A6nNL+pdFGtZQtND1eoY084HgHQ==", "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" + "@smithy/types": "^2.12.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" + "node_modules/@smithy/invalid-dependency": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.2.0.tgz", + "integrity": "sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + } }, - "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.36", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.36.tgz", - "integrity": "sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==", + "node_modules/@smithy/md5-js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.2.0.tgz", + "integrity": "sha512-M26XTtt9IIusVMOWEAhIvFIr9jYj4ISPPGJROqw6vXngO3IYJCnVVSMFn4Tx1rUTG5BiKJNg9u2nxmBiZC5IlQ==", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "@smithy/types": "^2.12.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "license": "MIT", + "node_modules/@smithy/middleware-content-length": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.2.0.tgz", + "integrity": "sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==", "dependencies": { - "@types/node": "*" + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "dev": true, - "license": "MIT" + "node_modules/@smithy/middleware-endpoint": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.1.tgz", + "integrity": "sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ==", + "dependencies": { + "@smithy/middleware-serde": "^2.3.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "@smithy/url-parser": "^2.2.0", + "@smithy/util-middleware": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@types/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" + "node_modules/@smithy/middleware-retry": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.3.1.tgz", + "integrity": "sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA==", + "dependencies": { + "@smithy/node-config-provider": "^2.3.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/service-error-classification": "^2.1.5", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-retry": "^2.2.0", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@types/ioredis-mock": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@types/ioredis-mock/-/ioredis-mock-8.2.2.tgz", - "integrity": "sha512-bnbPHOjxy4TUDjRh61MMoK2QvDNZqrMDXJYrEDZP/HPFvBubR24CQ0DBi5lgWhLxG4lvVsXPRDXtZ03+JgonoQ==", - "dev": true, - "peer": true, + "node_modules/@smithy/middleware-serde": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.3.0.tgz", + "integrity": "sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==", "dependencies": { - "ioredis": ">=5" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "license": "MIT" + "node_modules/@smithy/middleware-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.2.0.tgz", + "integrity": "sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==", + "dependencies": { + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "license": "MIT", + "node_modules/@smithy/node-config-provider": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.3.0.tgz", + "integrity": "sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==", "dependencies": { - "@types/istanbul-lib-coverage": "*" + "@smithy/property-provider": "^2.2.0", + "@smithy/shared-ini-file-loader": "^2.4.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "license": "MIT", + "node_modules/@smithy/node-http-handler": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.5.0.tgz", + "integrity": "sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==", "dependencies": { - "@types/istanbul-lib-report": "*" + "@smithy/abort-controller": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/querystring-builder": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/jest": { - "version": "29.5.2", - "dev": true, - "license": "MIT", + "node_modules/@smithy/property-provider": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.2.0.tgz", + "integrity": "sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==", "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "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==" - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, - "node_modules/@types/minimist": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", - "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==" - }, - "node_modules/@types/multer": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", - "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", + "node_modules/@smithy/protocol-http": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.3.0.tgz", + "integrity": "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==", "dependencies": { - "@types/express": "*" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/node": { - "version": "20.5.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", - "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" - }, - "node_modules/@types/node-int64": { - "version": "0.4.29", - "resolved": "https://registry.npmjs.org/@types/node-int64/-/node-int64-0.4.29.tgz", - "integrity": "sha512-rHXvenLTj/CcsmNAebaBOhxQ2MqEGl3yXZZcZ21XYR+gzGTTcpOy2N4IxpvTCz48loyQNatHvfn6GhIbbZ1R3Q==", + "node_modules/@smithy/querystring-builder": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.2.0.tgz", + "integrity": "sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==", "dependencies": { - "@types/node": "*" + "@smithy/types": "^2.12.0", + "@smithy/util-uri-escape": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" - }, - "node_modules/@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "license": "MIT" - }, - "node_modules/@types/q": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.5.tgz", - "integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "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, - "peer": true - }, - "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "node_modules/@smithy/querystring-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.2.0.tgz", + "integrity": "sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==", "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/serve-static": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", - "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "node_modules/@smithy/service-error-classification": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.5.tgz", + "integrity": "sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==", "dependencies": { - "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@smithy/types": "^2.12.0" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "license": "MIT" - }, - "node_modules/@types/strip-bom": { - "version": "3.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/strip-json-comments": { - "version": "0.0.30", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/superagent": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.18.tgz", - "integrity": "sha512-LOWgpacIV8GHhrsQU+QMZuomfqXiqzz3ILLkCtKx3Us6AmomFViuzKT9D693QTKgyut2oCytMG8/efOop+DB+w==", - "dev": true, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.4.0.tgz", + "integrity": "sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==", "dependencies": { - "@types/cookiejar": "*", - "@types/node": "*" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/supertest": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz", - "integrity": "sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ==", - "dev": true, + "node_modules/@smithy/signature-v4": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.3.0.tgz", + "integrity": "sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==", "dependencies": { - "@types/superagent": "*" + "@smithy/is-array-buffer": "^2.2.0", + "@smithy/types": "^2.12.0", + "@smithy/util-hex-encoding": "^2.2.0", + "@smithy/util-middleware": "^2.2.0", + "@smithy/util-uri-escape": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/thrift": { - "version": "0.10.13", - "resolved": "https://registry.npmjs.org/@types/thrift/-/thrift-0.10.13.tgz", - "integrity": "sha512-zNapgGgZP2tOC8zhS10LPKdJxH+U0owZ1WWwDZASgyb5HZGj03P4Wm+Yd3YDXDQEjSRqO2XQznUH13tcG4dkIA==", + "node_modules/@smithy/smithy-client": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.5.1.tgz", + "integrity": "sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ==", "dependencies": { - "@types/node": "*", - "@types/node-int64": "*", - "@types/q": "*" + "@smithy/middleware-endpoint": "^2.5.1", + "@smithy/middleware-stack": "^2.2.0", + "@smithy/protocol-http": "^3.3.0", + "@smithy/types": "^2.12.0", + "@smithy/util-stream": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/time-constants": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/uuid": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz", - "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==" + "node_modules/@smithy/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz", + "integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@types/validator": { - "version": "13.7.17", - "license": "MIT" + "node_modules/@smithy/url-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.2.0.tgz", + "integrity": "sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==", + "dependencies": { + "@smithy/querystring-parser": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + } }, - "node_modules/@types/yargs": { - "version": "17.0.24", - "license": "MIT", + "node_modules/@smithy/util-base64": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.3.0.tgz", + "integrity": "sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==", "dependencies": { - "@types/yargs-parser": "*" + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "license": "MIT" + "node_modules/@smithy/util-body-length-browser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.2.0.tgz", + "integrity": "sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==", + "dependencies": { + "tslib": "^2.6.2" + } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.7.0.tgz", - "integrity": "sha512-GJWR0YnfrKnsRoluVO3PRb9r5aMZriiMMM/RHj5nnTrBy1/wIgk76XCtCKcnXGjpZQJQRFtGV9/0JJ6n30uwpQ==", - "dev": true, - "peer": true, + "node_modules/@smithy/util-body-length-node": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.3.0.tgz", + "integrity": "sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.7.0", - "@typescript-eslint/type-utils": "7.7.0", - "@typescript-eslint/utils": "7.7.0", - "@typescript-eslint/visitor-keys": "7.7.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=14.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.7.0.tgz", - "integrity": "sha512-fNcDm3wSwVM8QYL4HKVBggdIPAy9Q41vcvC/GtDobw3c4ndVT3K6cqudUmjHPw8EAp4ufax0o58/xvWaP2FmTg==", - "dev": true, + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", "dependencies": { - "@typescript-eslint/scope-manager": "7.7.0", - "@typescript-eslint/types": "7.7.0", - "@typescript-eslint/typescript-estree": "7.7.0", - "@typescript-eslint/visitor-keys": "7.7.0", - "debug": "^4.3.4" + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=14.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.7.0.tgz", - "integrity": "sha512-/8INDn0YLInbe9Wt7dK4cXLDYp0fNHP5xKLHvZl3mOT5X17rK/YShXaiNmorl+/U4VKCVIjJnx4Ri5b0y+HClw==", - "dev": true, + "node_modules/@smithy/util-config-provider": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.3.0.tgz", + "integrity": "sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==", "dependencies": { - "@typescript-eslint/types": "7.7.0", - "@typescript-eslint/visitor-keys": "7.7.0" + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=14.0.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.7.0.tgz", - "integrity": "sha512-bOp3ejoRYrhAlnT/bozNQi3nio9tIgv3U5C0mVDdZC7cpcQEDZXvq8inrHYghLVwuNABRqrMW5tzAv88Vy77Sg==", - "dev": true, - "peer": true, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.1.tgz", + "integrity": "sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw==", "dependencies": { - "@typescript-eslint/typescript-estree": "7.7.0", - "@typescript-eslint/utils": "7.7.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@smithy/property-provider": "^2.2.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 10.0.0" } }, - "node_modules/@typescript-eslint/types": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.7.0.tgz", - "integrity": "sha512-G01YPZ1Bd2hn+KPpIbrAhEWOn5lQBrjxkzHkWvP6NucMXFtfXoevK82hzQdpfuQYuhkvFDeQYbzXCjR1z9Z03w==", - "dev": true, - "engines": { - "node": "^18.18.0 || >=20.0.0" + "node_modules/@smithy/util-defaults-mode-node": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.1.tgz", + "integrity": "sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA==", + "dependencies": { + "@smithy/config-resolver": "^2.2.0", + "@smithy/credential-provider-imds": "^2.3.0", + "@smithy/node-config-provider": "^2.3.0", + "@smithy/property-provider": "^2.2.0", + "@smithy/smithy-client": "^2.5.1", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": ">= 10.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.7.0.tgz", - "integrity": "sha512-8p71HQPE6CbxIBy2kWHqM1KGrC07pk6RJn40n0DSc6bMOBBREZxSDJ+BmRzc8B5OdaMh1ty3mkuWRg4sCFiDQQ==", - "dev": true, + "node_modules/@smithy/util-endpoints": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.2.0.tgz", + "integrity": "sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==", "dependencies": { - "@typescript-eslint/types": "7.7.0", - "@typescript-eslint/visitor-keys": "7.7.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@smithy/node-config-provider": "^2.3.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 14.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/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, + "node_modules/@smithy/util-hex-encoding": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz", + "integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==", "dependencies": { - "balanced-match": "^1.0.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, + "node_modules/@smithy/util-middleware": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.2.0.tgz", + "integrity": "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==", "dependencies": { - "brace-expansion": "^2.0.1" + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=14.0.0" } }, - "node_modules/@typescript-eslint/utils": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.7.0.tgz", - "integrity": "sha512-LKGAXMPQs8U/zMRFXDZOzmMKgFv3COlxUQ+2NMPhbqgVm6R1w+nU1i4836Pmxu9jZAuIeyySNrN/6Rc657ggig==", - "dev": true, - "peer": true, + "node_modules/@smithy/util-retry": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.2.0.tgz", + "integrity": "sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.15", - "@types/semver": "^7.5.8", - "@typescript-eslint/scope-manager": "7.7.0", - "@typescript-eslint/types": "7.7.0", - "@typescript-eslint/typescript-estree": "7.7.0", - "semver": "^7.6.0" + "@smithy/service-error-classification": "^2.1.5", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" + "node": ">= 14.0.0" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.7.0.tgz", - "integrity": "sha512-h0WHOj8MhdhY8YWkzIF30R379y0NqyOHExI9N9KCzvmu05EgG4FumeYa3ccfKUSphyWkWQE1ybVrgz/Pbam6YA==", - "dev": true, + "node_modules/@smithy/util-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.2.0.tgz", + "integrity": "sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==", "dependencies": { - "@typescript-eslint/types": "7.7.0", - "eslint-visitor-keys": "^3.4.3" + "@smithy/fetch-http-handler": "^2.5.0", + "@smithy/node-http-handler": "^2.5.0", + "@smithy/types": "^2.12.0", + "@smithy/util-base64": "^2.3.0", + "@smithy/util-buffer-from": "^2.2.0", + "@smithy/util-hex-encoding": "^2.2.0", + "@smithy/util-utf8": "^2.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=14.0.0" } }, - "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 + "node_modules/@smithy/util-uri-escape": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz", + "integrity": "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "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==" + "node_modules/@smithy/util-waiter": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.2.0.tgz", + "integrity": "sha512-IHk53BVw6MPMi2Gsn+hCng8rFA3ZmR3Rk7GllxDUW9qFJl/hiSvskn7XldkECapQVkIg/1dHpMAxI9xSTaLLSA==", + "dependencies": { + "@smithy/abort-controller": "^2.2.0", + "@smithy/types": "^2.12.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } }, - "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==" + "node_modules/@songkeys/nestjs-redis": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@songkeys/nestjs-redis/-/nestjs-redis-10.0.0.tgz", + "integrity": "sha512-s56+NECuJXzcaPLYzpvA2xjL0e/1Zy55UE0q6b1UqpbQSKI06TFPFCWCMUadJigiuB26O1hxi+lmDbzahKvcLg==", + "dependencies": { + "tslib": "2.6.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "ioredis": "^5.0.0" + } }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==" + "node_modules/@songkeys/nestjs-redis/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, - "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==", + "node_modules/@substrate/connect": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/@substrate/connect/-/connect-0.8.8.tgz", + "integrity": "sha512-zwaxuNEVI9bGt0rT8PEJiXOyebLIo6QN1SyiAHRPBOl6g3Sy0KKdSN8Jmyn++oXhVRD8aIe75/V8ZkS81T+BPQ==", + "optional": true, "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" + "@substrate/connect-extension-protocol": "^2.0.0", + "@substrate/connect-known-chains": "^1.1.1", + "@substrate/light-client-extension-helpers": "^0.0.4", + "smoldot": "2.0.22" } }, - "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==" + "node_modules/@substrate/connect-extension-protocol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@substrate/connect-extension-protocol/-/connect-extension-protocol-2.0.0.tgz", + "integrity": "sha512-nKu8pDrE3LNCEgJjZe1iGXzaD6OSIDD4Xzz/yo4KO9mQ6LBvf49BVrt4qxBFGL6++NneLiWUZGoh+VSd4PyVIg==", + "optional": true }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "node_modules/@substrate/connect-known-chains": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@substrate/connect-known-chains/-/connect-known-chains-1.1.4.tgz", + "integrity": "sha512-iT+BdKqvKl/uBLd8BAJysFM1BaMZXRkaXBP2B7V7ob/EyNs5h0EMhTVbO6MJxV/IEOg5OKsyl6FUqQK7pKnqyw==", + "optional": true + }, + "node_modules/@substrate/light-client-extension-helpers": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@substrate/light-client-extension-helpers/-/light-client-extension-helpers-0.0.4.tgz", + "integrity": "sha512-vfKcigzL0SpiK+u9sX6dq2lQSDtuFLOxIJx2CKPouPEHIs8C+fpsufn52r19GQn+qDhU8POMPHOVoqLktj8UEA==", + "optional": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@polkadot-api/client": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@polkadot-api/json-rpc-provider": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@polkadot-api/json-rpc-provider-proxy": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@polkadot-api/substrate-client": "0.0.1-492c132563ea6b40ae1fc5470dec4cd18768d182.1.0", + "@substrate/connect-extension-protocol": "^2.0.0", + "@substrate/connect-known-chains": "^1.1.1", + "rxjs": "^7.8.1" + }, + "peerDependencies": { + "smoldot": "2.x" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "node_modules/@substrate/ss58-registry": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@substrate/ss58-registry/-/ss58-registry-1.47.0.tgz", + "integrity": "sha512-6kuIJedRcisUJS2pgksEH2jZf3hfSIVzqtFzs/AyjTW3ETbMg5q1Bb7VWa0WYaT6dTrEXp/6UoXM5B9pSIUmcw==" + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" } }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "dependencies": { - "@xtuc/long": "4.2.2" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "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==" + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, "dependencies": { - "@webassemblyjs/ast": "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" + "@babel/types": "^7.20.7" } }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "node_modules/@types/bn.js": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", + "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@types/node": "*" } }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "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==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@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" + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" + "@types/node": "*" } }, - "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==" + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true }, - "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==" + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } }, - "node_modules/accepts": { - "version": "1.3.8", - "license": "MIT", + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "@types/eslint": "*", + "@types/estree": "*" } }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "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==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "peerDependencies": { - "acorn": "^8" + "node_modules/@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "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==", + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "dependencies": { + "@types/node": "*" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true + }, + "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==" + }, + "node_modules/@types/ioredis-mock": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/@types/ioredis-mock/-/ioredis-mock-8.2.5.tgz", + "integrity": "sha512-cZyuwC9LGtg7s5G9/w6rpy3IOZ6F/hFR0pQlWYZESMo1xQUYbDpa6haqB4grTePjsGzcB/YLBFCjqRunK5wieg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "ioredis": ">=5" } }, - "node_modules/ajv": { - "version": "6.12.6", - "license": "MIT", + "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 + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": 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" + "@types/istanbul-lib-coverage": "*" } }, - "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==", + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "@types/istanbul-lib-report": "*" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "expect": "^29.0.0", + "pretty-format": "^29.0.0" } }, - "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==" + "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==" }, - "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==", - "peerDependencies": { - "ajv": "^6.9.1" - } + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "engines": { - "node": ">=6" + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": 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==" + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==" + }, + "node_modules/@types/multer": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", + "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "dependencies": { + "@types/express": "*" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "license": "MIT", + "node_modules/@types/node": { + "version": "20.12.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", + "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "undici-types": "~5.26.4" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/@types/node-int64": { + "version": "0.4.32", + "resolved": "https://registry.npmjs.org/@types/node-int64/-/node-int64-0.4.32.tgz", + "integrity": "sha512-xf/JsSlnXQ+mzvc0IpXemcrO4BrCfpgNpMco+GLcXkFk01k/gW9lGJu+Vof0ZSvHK6DsHJDPSbjFPs36QkWXqw==", + "dependencies": { + "@types/node": "*" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==" + }, + "node_modules/@types/q": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", + "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==" + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" + }, + "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==" + }, + "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 + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", + "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==", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "license": "MIT", - "peer": true + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true }, - "node_modules/anymatch": { - "version": "3.1.3", - "license": "ISC", + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, + "node_modules/@types/superagent": { + "version": "8.1.7", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.7.tgz", + "integrity": "sha512-NmIsd0Yj4DDhftfWvvAku482PZum4DBW7U51OvS8gvOkDDY0WT1jsVyDV3hK+vplrsYw8oDwi9QxOM7U68iwww==", + "dev": true, "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*" } }, - "node_modules/app-root-path": { - "version": "3.1.0", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 6.0.0" + "node_modules/@types/supertest": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "dev": true, + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" } }, - "node_modules/append-field": { - "version": "1.0.0", - "license": "MIT" + "node_modules/@types/thrift": { + "version": "0.10.17", + "resolved": "https://registry.npmjs.org/@types/thrift/-/thrift-0.10.17.tgz", + "integrity": "sha512-bDX6d5a5ZDWC81tgDv224n/3PKNYfIQJTPHzlbk4vBWJrYXF6Tg1ncaVmP/c3JbGN2AK9p7zmHorJC2D6oejGQ==", + "dependencies": { + "@types/node": "*", + "@types/node-int64": "*", + "@types/q": "*" + } }, - "node_modules/arg": { - "version": "4.1.3", - "license": "MIT" + "node_modules/@types/time-constants": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/time-constants/-/time-constants-1.0.2.tgz", + "integrity": "sha512-ttK/moi86ZT9UE6yRnLGKZBBEvoHiSD2tK46nR+7/7Vt5Mp/22AaB7Eng0dRFFs6Ieur0Cj8oBwuX4OxC1Zr7g==", + "dev": true }, - "node_modules/argparse": { - "version": "2.0.1", - "license": "Python-2.0" + "node_modules/@types/validator": { + "version": "13.11.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.10.tgz", + "integrity": "sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg==" }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.0", + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/yargs-parser": "*" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "license": "MIT" + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true }, - "node_modules/array-includes": { - "version": "3.1.6", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", + "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/type-utils": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/array-timsort": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", - "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==" - }, - "node_modules/array-union": { - "version": "2.1.0", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", + "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", + "node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" + "ms": "2.1.2" }, "engines": { - "node": ">= 0.4" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "engines": { - "node": ">=0.10.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/async": { - "version": "3.2.3", - "license": "MIT" - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.5", + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", + "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", "dev": true, - "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0" + }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || >=20.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.4.0", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/babel-jest": { - "version": "29.5.0", + "node_modules/@typescript-eslint/type-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz", + "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/transform": "^29.5.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" + "eslint": "^8.56.0" }, - "engines": { - "node": ">=8" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", + "node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "ms": "2.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "node": ">=6.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/babel-preset-jest": { - "version": "29.5.0", + "node_modules/@typescript-eslint/type-utils/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/@typescript-eslint/types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", + "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "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/big-integer": { - "version": "1.6.51", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", + "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", "dev": true, - "license": "Unlicense", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/bl/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==", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" - }, - "node_modules/bn.js": { - "version": "5.2.1", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "1.20.2", - "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.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" + "node": "^18.18.0 || >=20.0.0" }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", + "node_modules/@typescript-eslint/typescript-estree/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, "dependencies": { - "ms": "2.0.0" + "balanced-match": "^1.0.0" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, - "node_modules/bplist-parser": { - "version": "0.2.0", + "node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", "dependencies": { - "big-integer": "^1.6.44" + "ms": "2.1.2" }, "engines": { - "node": ">= 5.10.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/braces": { - "version": "3.0.2", - "license": "MIT", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=8" - } - }, - "node_modules/browser-or-node": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz", - "integrity": "sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg==" - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/browserify-zlib/node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "node_modules/browserslist": { - "version": "4.21.8", - "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.30001502", - "electron-to-chromium": "^1.4.428", - "node-releases": "^2.0.12", - "update-browserslist-db": "^1.0.11" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/bson": { - "version": "4.6.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.3.tgz", - "integrity": "sha512-rAqP5hcUVJhXP2MCSNVsf0oM2OGU1So6A9pVRDYayvJ5+hygXHQApf87wd5NlhPM1J9RJnbqxIG/f8QTzRoQ4A==", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/bson/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "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/buffer-from": { - "version": "1.1.2", - "license": "MIT" - }, - "node_modules/builtins": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, - "node_modules/bullmq": { - "version": "3.15.8", - "license": "MIT", - "dependencies": { - "cron-parser": "^4.6.0", - "glob": "^8.0.3", - "ioredis": "^5.3.2", - "lodash": "^4.17.21", - "msgpackr": "^1.6.2", - "semver": "^7.3.7", - "tslib": "^2.0.0", - "uuid": "^9.0.0" - } - }, - "node_modules/bullmq/node_modules/brace-expansion": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/bullmq/node_modules/glob": { - "version": "8.1.0", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/bullmq/node_modules/minimatch": { - "version": "5.1.6", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/bundle-name": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cache-manager": { - "version": "4.1.0", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "async": "3.2.3", - "lodash.clonedeep": "^4.5.0", - "lru-cache": "^7.10.1" - } - }, - "node_modules/cache-manager/node_modules/lru-cache": { - "version": "7.18.3", - "license": "ISC", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "^4.0.1", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.2", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-keys": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", - "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dependencies": { - "camelcase": "^5.3.1", - "map-obj": "^4.0.0", - "quick-lru": "^4.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/camelcase-keys/node_modules/quick-lru": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "engines": { - "node": ">=8" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001502", - "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": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - }, - "node_modules/chokidar": { - "version": "3.5.3", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "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" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.8.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cids": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.9.tgz", - "integrity": "sha512-l11hWRfugIcbGuTZwAM5PwpjPPjyb6UZOGwlHSnOBV5o07XhQ4gNpBN67FbODvpjyHtd+0Xs6KNvUcGBiDRsdg==", - "deprecated": "This module has been superseded by the multiformats module", - "dependencies": { - "multibase": "^4.0.1", - "multicodec": "^3.0.1", - "multihashes": "^4.0.1", - "uint8arrays": "^3.0.0" - }, - "engines": { - "node": ">=4.0.0", - "npm": ">=3.0.0" - } - }, - "node_modules/cids/node_modules/uint8arrays": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", - "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", - "dependencies": { - "multiformats": "^9.4.2" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/class-transformer": { - "version": "0.5.1", - "license": "MIT" - }, - "node_modules/class-validator": { - "version": "0.14.0", - "license": "MIT", - "dependencies": { - "@types/validator": "^13.7.10", - "libphonenumber-js": "^1.10.14", - "validator": "^13.7.0" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "license": "ISC", - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/cliui": { - "version": "7.0.4", - "license": "ISC", - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cli-highlight/node_modules/yargs": { - "version": "16.2.0", - "license": "MIT", - "peer": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cli-highlight/node_modules/yargs-parser": { - "version": "20.2.9", - "license": "ISC", - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", - "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "engines": { - "node": ">= 10" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "dev": true, - "license": "MIT" + "node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", + "node_modules/@typescript-eslint/utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz", + "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==", + "dev": true, "dependencies": { - "color-name": "~1.1.4" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "semver": "^7.6.0" }, "engines": { - "node": ">=7.0.0" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "license": "MIT", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", + "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", + "dev": true, "dependencies": { - "delayed-stream": "~1.0.0" + "@typescript-eslint/types": "7.8.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">= 0.8" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "engines": { - "node": ">= 6" + "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 + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, - "node_modules/comment-json": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", - "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", + "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==" + }, + "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==" + }, + "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==" + }, + "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==", "dependencies": { - "array-timsort": "^1.0.3", - "core-util-is": "^1.0.3", - "esprima": "^4.0.1", - "has-own-prop": "^2.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">= 6" + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" } }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "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==" }, - "node_modules/concat-map": { - "version": "0.0.1", - "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==", + "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/concat-stream": { - "version": "1.6.2", - "engines": [ - "node >= 0.8" - ], - "license": "MIT", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "dev": true, - "license": "MIT" + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dependencies": { + "@xtuc/long": "4.2.2" + } }, - "node_modules/consola": { - "version": "2.15.3", - "license": "MIT" + "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==" }, - "node_modules/content-disposition": { - "version": "0.5.4", - "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==", "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" + "@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/content-type": { - "version": "1.0.5", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "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==", + "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/convert-source-map": { - "version": "2.0.0", - "license": "MIT" + "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==", + "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/cookie": { - "version": "0.5.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "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==", + "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/cookie-signature": { - "version": "1.0.6", - "license": "MIT" + "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==", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": 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==" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "license": "MIT" + "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==" }, - "node_modules/cors": { - "version": "2.8.5", - "license": "MIT", + "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==", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.6" } }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "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" + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=10" + "node": ">=0.4.0" } }, - "node_modules/create-require": { - "version": "1.1.1", - "license": "MIT" + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "peerDependencies": { + "acorn": "^8" + } }, - "node_modules/cron": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/cron/-/cron-2.4.1.tgz", - "integrity": "sha512-ty0hUSPuENwDtIShDFxUxWEIsqiu2vhoFtt6Vwrbg4lHGtJX2/cV2p0hH6/qaEM9Pj+i6mQoau48BO5wBpkP4w==", - "dependencies": { - "luxon": "^3.2.1" + "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, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/cron-parser": { - "version": "4.8.1", - "license": "MIT", - "dependencies": { - "luxon": "^3.2.1" - }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "engines": { - "node": ">=12.0.0" + "node": ">=0.4.0" } }, - "node_modules/cross-fetch": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", - "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dependencies": { - "node-fetch": "^2.6.12" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "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==", "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" + "ajv": "^8.0.0" }, "peerDependencies": { - "encoding": "^0.1.0" + "ajv": "^8.0.0" }, "peerDependenciesMeta": { - "encoding": { + "ajv": { "optional": true } } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "license": "MIT", + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "type-fest": "^0.21.3" }, "engines": { - "node": ">= 8" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "license": "MIT", + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "engines": { - "node": ">= 12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/date-fns": { - "version": "2.30.0", - "license": "MIT", - "peer": true, + "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==", + "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==", "dependencies": { - "@babel/runtime": "^7.21.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.11" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/debug": { - "version": "4.3.4", - "license": "MIT", + "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==", "dependencies": { - "ms": "2.1.2" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 8" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "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==", "engines": { - "node": ">=0.10.0" + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/decamelize-keys": { + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, + "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==" + }, + "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", - "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", - "dependencies": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==" + }, + "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, "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/decamelize-keys/node_modules/map-obj": { + "node_modules/arrify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "engines": { "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "6.0.0", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, - "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=8" } }, - "node_modules/dedent": { - "version": "0.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-extend": { - "version": "0.6.0", + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, - "license": "MIT", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, "engines": { - "node": ">=4.0.0" + "node": ">=8" } }, - "node_modules/deep-is": { - "version": "0.1.4", + "node_modules/babel-plugin-istanbul/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": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "license": "MIT", - "engines": { - "node": ">=0.10.0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/default-browser": { - "version": "4.0.0", + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, - "license": "MIT", "dependencies": { - "bundle-name": "^3.0.0", - "default-browser-id": "^3.0.0", - "execa": "^7.1.1", - "titleize": "^3.0.0" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/default-browser-id": { - "version": "3.0.0", + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, - "license": "MIT", "dependencies": { - "bplist-parser": "^0.2.0", - "untildify": "^4.0.0" - }, - "engines": { - "node": ">=12" + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, "dependencies": { - "clone": "^1.0.2" + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "dev": true, - "license": "MIT", "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "dev": true, - "license": "MIT", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "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" + } + ] + }, + "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==", "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-properties": { - "version": "1.2.0", - "dev": true, - "license": "MIT", + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=0.4.0" + "node_modules/bl/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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/denque": { - "version": "2.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==" }, - "node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, - "node_modules/destroy": { - "version": "1.2.0", - "license": "MIT", + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "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.11.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/detect-newline": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, + "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==", "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/diff": { - "version": "4.0.2", - "license": "BSD-3-Clause", + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, "engines": { - "node": ">=0.3.1" + "node": ">=8" } }, - "node_modules/diff-sequences": { - "version": "29.4.3", - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } + "node_modules/brotli-wasm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brotli-wasm/-/brotli-wasm-2.0.1.tgz", + "integrity": "sha512-+3USgYsC7bzb5yU0/p2HnnynZl0ak0E6uoIm4UW4Aby/8s8HFCq6NCfrrf1E9c3O8OCSzq3oYO1tUVqIi61Nww==" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", + "node_modules/browser-or-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.3.0.tgz", + "integrity": "sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg==" + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" + "pako": "~1.0.5" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", + "node_modules/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "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" + } + ], "dependencies": { - "esutils": "^2.0.2" + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">=6.0.0" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/dotenv": { - "version": "16.3.1", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" - } - }, - "node_modules/dotenv-expand": { - "version": "10.0.0", - "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">= 6" } }, - "node_modules/dynamic-dedupe": { - "version": "0.3.0", + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, - "license": "MIT", "dependencies": { - "xtend": "^4.0.0" + "node-int64": "^0.4.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/ejs": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.7.tgz", - "integrity": "sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==", + "node_modules/bson": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.6.3.tgz", + "integrity": "sha512-rAqP5hcUVJhXP2MCSNVsf0oM2OGU1So6A9pVRDYayvJ5+hygXHQApf87wd5NlhPM1J9RJnbqxIG/f8QTzRoQ4A==", "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" + "buffer": "^5.6.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.4.429", - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } + "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==" }, - "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==", + "node_modules/bullmq": { + "version": "5.7.8", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.7.8.tgz", + "integrity": "sha512-F/Haeu6AVHkFrfeaU/kLOjhfrH6x3CaKAZlQQ+76fa8l3kfI9oaUHeFMW+1mYVz0NtYPF7PNTWFq4ylAHYcCgA==", "dependencies": { - "once": "^1.4.0" + "cron-parser": "^4.6.0", + "ioredis": "^5.4.1", + "msgpackr": "^1.10.1", + "node-abort-controller": "^3.1.1", + "semver": "^7.5.4", + "tslib": "^2.0.0", + "uuid": "^9.0.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "streamsearch": "^1.1.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" } }, - "node_modules/eol": { - "version": "0.9.1", + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", "dev": true, - "license": "MIT" - }, - "node_modules/err-code": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", - "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + "engines": { + "node": ">=14.16" + } }, - "node_modules/error-ex": { - "version": "1.3.2", - "license": "MIT", + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, "dependencies": { - "is-arrayish": "^0.2.1" + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" } }, - "node_modules/es-abstract": { - "version": "1.21.2", - "dev": true, - "license": "MIT", + "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==", "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "es-set-tostringtag": "^2.0.1", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", - "get-symbol-description": "^1.0.0", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.3", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", - "typed-array-length": "^1.0.4", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "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" @@ -5589,1367 +6199,1545 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==" - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3", - "has": "^1.0.3", - "has-tostringtag": "^1.0.0" - }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "engines": { - "node": ">= 0.4" + "node": ">=6" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" } }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "dev": true, - "license": "MIT", + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escalade": { - "version": "3.1.1", - "license": "MIT", + "node_modules/camelcase-keys/node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "license": "MIT" + "node_modules/caniuse-lite": { + "version": "1.0.30001617", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001617.tgz", + "integrity": "sha512-mLyjzNI9I+Pix8zwcrpxEbGlfqOkF9kM3ptzmKNw5tizSyYwMe+nGLTqMK9cO+0E+Bh6TsBxNAaHWEM8xwSsmA==", + "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" + } + ] }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@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" + "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 8.10.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "engines": { + "node": ">=6.0" } }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", + "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, - "license": "MIT", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cids": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/cids/-/cids-1.1.9.tgz", + "integrity": "sha512-l11hWRfugIcbGuTZwAM5PwpjPPjyb6UZOGwlHSnOBV5o07XhQ4gNpBN67FbODvpjyHtd+0Xs6KNvUcGBiDRsdg==", + "deprecated": "This module has been superseded by the multiformats module", "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" + "multibase": "^4.0.1", + "multicodec": "^3.0.1", + "multihashes": "^4.0.1", + "uint8arrays": "^3.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" + "node": ">=4.0.0", + "npm": ">=3.0.0" } }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/cids/node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "dependencies": { + "multiformats": "^9.4.2" } }, - "node_modules/eslint-config-airbnb-typescript": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-18.0.0.tgz", - "integrity": "sha512-oc+Lxzgzsu8FQyFVa4QFaVKiitTYiiW3frB9KYW5OWdPrqFc7FzxgB20hP4cHMlr+MBzGcLl3jnCOVOydL9mIg==", - "dev": true, + "node_modules/cjs-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", + "dev": true + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", "dependencies": { - "eslint-config-airbnb-base": "^15.0.0" + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" }, - "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" + "engines": { + "node": ">=8" } }, - "node_modules/eslint-config-prettier": { - "version": "8.8.0", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" }, - "peerDependencies": { - "eslint": ">=7.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "dev": true, - "license": "MIT", + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "engines": { + "node": ">= 10" } }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.5.5", + "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": { - "debug": "^4.3.4", - "enhanced-resolve": "^5.12.0", - "eslint-module-utils": "^2.7.4", - "get-tsconfig": "^4.5.0", - "globby": "^13.1.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "synckit": "^0.8.5" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*" + "node": ">=12" } }, - "node_modules/eslint-import-resolver-typescript/node_modules/globby": { - "version": "13.1.4", + "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": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.11", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^4.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/eslint-import-resolver-typescript/node_modules/slash": { - "version": "4.0.0", - "dev": true, - "license": "MIT", + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.8" } }, - "node_modules/eslint-module-utils": { - "version": "2.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.2.7" - }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, - "node_modules/eslint-plugin-es": { - "version": "4.1.0", - "dev": true, - "license": "MIT", + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "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==", "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" + "node": ">=7.0.0" } }, - "node_modules/eslint-plugin-es/node_modules/eslint-utils": { - "version": "2.1.0", - "dev": true, - "license": "MIT", + "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==" + }, + "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==", "dependencies": { - "eslint-visitor-keys": "^1.1.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "node": ">= 0.8" } }, - "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "dev": true, - "license": "Apache-2.0", + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "engines": { - "node": ">=4" + "node": ">= 6" } }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "dev": true, - "license": "MIT", + "node_modules/comment-json": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.3.tgz", + "integrity": "sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "node": ">= 6" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, - "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/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==", "dependencies": { - "ms": "^2.1.1" + "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/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", + "node_modules/concat-stream/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==" + }, + "node_modules/concat-stream/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==", "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" + "safe-buffer": "~5.1.0" } }, - "node_modules/eslint-plugin-import/node_modules/json5": { - "version": "1.0.2", - "dev": true, - "license": "MIT", + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + }, + "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==", "dependencies": { - "minimist": "^1.2.0" + "safe-buffer": "5.2.1" }, - "bin": { - "json5": "lib/cli.js" + "engines": { + "node": ">= 0.6" } }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "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==", + "engines": { + "node": ">= 0.6" } }, - "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { - "version": "3.14.2", - "dev": true, - "license": "MIT", + "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 + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "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==" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true + }, + "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==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" } }, - "node_modules/eslint-plugin-n": { - "version": "15.7.0", - "dev": true, - "license": "MIT", + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dependencies": { - "builtins": "^5.0.1", - "eslint-plugin-es": "^4.1.0", - "eslint-utils": "^3.0.0", - "ignore": "^5.1.1", - "is-core-module": "^2.11.0", - "minimatch": "^3.1.2", - "resolve": "^1.22.1", - "semver": "^7.3.8" + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" }, "engines": { - "node": ">=12.22.0" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" + "url": "https://github.com/sponsors/d-fischer" }, "peerDependencies": { - "eslint": ">=7.0.0" + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/eslint-plugin-nestjs": { - "version": "1.2.3", + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, - "license": "MIT", "dependencies": { - "tslib": "^1.8.1" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" }, "engines": { - "npm": ">=3" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/eslint-plugin-nestjs/node_modules/tslib": { - "version": "1.14.1", - "dev": true, - "license": "0BSD" + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, - "node_modules/eslint-plugin-prettier": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz", - "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==", - "dev": true, + "node_modules/cron": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", + "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", + "dependencies": { + "@types/luxon": "~3.4.0", + "luxon": "~3.4.0" + } + }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", "dependencies": { - "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.5" + "luxon": "^3.2.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/prettier" - }, - "peerDependencies": { - "@types/eslint": ">=8.0.0", - "eslint": ">=8.0.0", - "prettier": ">=3.0.0" + "node": ">=12.0.0" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "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==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint-config-prettier": { - "optional": true - } + "engines": { + "node": ">= 8" } }, - "node_modules/eslint-plugin-promise": { - "version": "6.1.1", - "dev": true, - "license": "ISC", + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "node": ">= 12" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "license": "BSD-2-Clause", + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, + "ms": "2.0.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "engines": { - "node": ">=8.0.0" + "node": ">=0.10.0" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "dev": true, - "license": "MIT", + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", "dependencies": { - "eslint-visitor-keys": "^2.0.0" + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + "node": ">=0.10.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "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==", + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, - "engines": { - "node": ">=4.0" + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } } }, - "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==", + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": 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": ">=4.0.0" } }, - "node_modules/esprima": { - "version": "4.0.1", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, + "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 + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/esquery": { - "version": "1.5.0", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dependencies": { - "estraverse": "^5.1.0" + "clone": "^1.0.2" }, - "engines": { - "node": ">=0.10" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", "dev": true, - "license": "BSD-2-Clause", "engines": { - "node": ">=4.0" + "node": ">=10" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "license": "BSD-2-Clause", + "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==", "dependencies": { - "estraverse": "^5.2.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=4.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "license": "BSD-2-Clause", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "engines": { - "node": ">=4.0" + "node": ">=0.4.0" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "license": "BSD-2-Clause", + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", "engines": { - "node": ">=4.0" + "node": ">=0.10" } }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", + "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==", "engines": { - "node": ">=0.10.0" + "node": ">= 0.8" } }, - "node_modules/etag": { - "version": "1.8.1", - "license": "MIT", + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/eventemitter2": { - "version": "6.4.9", - "license": "MIT" - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "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==", + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, "engines": { - "node": ">=0.8.x" + "node": ">=8" } }, - "node_modules/execa": { - "version": "7.1.1", + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, - "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.1", - "human-signals": "^4.3.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^3.0.7", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": "^14.18.0 || ^16.14.0 || >=18.0.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "asap": "^2.0.0", + "wrappy": "1" } }, - "node_modules/exit": { - "version": "0.1.2", - "dev": true, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "engines": { - "node": ">= 0.8.0" + "node": ">=0.3.1" } }, - "node_modules/expect": { - "version": "29.5.0", - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" - }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/express": { - "version": "4.18.2", - "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, "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "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, + "dependencies": { + "esutils": "^2.0.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">=6.0.0" } }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "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.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.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==", + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=12" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, "dependencies": { - "ms": "2.0.0" + "xtend": "^4.0.0" } }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "license": "MIT" + "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==" }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "license": "MIT", + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.763", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.763.tgz", + "integrity": "sha512-k4J8NrtJ9QrvHLRo8Q18OncqBCB7tIUyqxRcJnlonQ0ioHKYB988GcDFF3ZePmnb8eHEopDs/wPHR/iGAFgoUQ==" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "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==" + }, + "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==", "engines": { "node": ">= 0.8" } }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "node_modules/enhanced-resolve": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" }, "engines": { - "node": ">=4" + "node": ">=10.13.0" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" + "node_modules/eol": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", + "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==", + "dev": true }, - "node_modules/fast-diff": { - "version": "1.3.0", - "dev": true, - "license": "Apache-2.0" + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" }, - "node_modules/fast-glob": { - "version": "3.2.12", - "dev": true, - "license": "MIT", + "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==", "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" + "is-arrayish": "^0.2.1" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", + "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==", "dependencies": { - "is-glob": "^4.0.1" + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" + "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==", + "engines": { + "node": ">= 0.4" + } }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "license": "MIT" + "node_modules/es-module-lexer": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz", + "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==" }, - "node_modules/fastq": { - "version": "1.15.0", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" } }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } + "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==" }, - "node_modules/fengari": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.4.tgz", - "integrity": "sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==", + "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, - "dependencies": { - "readline-sync": "^1.4.9", - "sprintf-js": "^1.1.1", - "tmp": "^0.0.33" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/fengari-interop": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/fengari-interop/-/fengari-interop-0.1.3.tgz", - "integrity": "sha512-EtZ+oTu3kEwVJnoymFPBVLIbQcCoy9uWCVnMA6h3M/RqHkUBsLYp29+RRHf9rKr6GwjubWREU1O7RretFIXjHw==", + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, - "peerDependencies": { - "fengari": "^0.1.0" + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@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/fengari/node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", - "dev": true - }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", + "node_modules/eslint-plugin-jest": { + "version": "28.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.5.0.tgz", + "integrity": "sha512-6np6DGdmNq/eBbA7HOUNV8fkfL86PYwBfwyb8n23FXgJNTR8+ot3smRHjza9LGsBBZRypK3qyF79vMjohIL8eQ==", + "dev": true, "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0" }, "engines": { - "node": "^12.20 || >= 14.13" + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "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, "dependencies": { - "escape-string-regexp": "^1.0.5" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/figures/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==", + "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, "engines": { - "node": ">=0.8.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/file-entry-cache": { - "version": "6.0.1", + "node_modules/eslint/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": { - "flat-cache": "^3.0.4" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": "^10.12.0 || >=12.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "node_modules/eslint/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, "dependencies": { - "minimatch": "^5.0.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/filelist/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==", + "node_modules/eslint/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, "dependencies": { - "balanced-match": "^1.0.0" + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" } }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/eslint/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 + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "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, "dependencies": { - "brace-expansion": "^2.0.1" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/fill-range": { - "version": "7.0.1", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/finalhandler": { - "version": "1.2.0", - "license": "MIT", + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" + "estraverse": "^5.1.0" }, "engines": { - "node": ">= 0.8" + "node": ">=0.10" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", + "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==", "dependencies": { - "ms": "2.0.0" + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } }, - "node_modules/find-up": { - "version": "5.0.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": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/flat-cache": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">= 0.6" } }, - "node_modules/flatted": { - "version": "3.2.7", - "dev": true, - "license": "ISC" + "node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "node": ">=0.8.x" } }, - "node_modules/for-each": { - "version": "0.3.3", + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "license": "MIT", "dependencies": { - "is-callable": "^1.1.3" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "node_modules/execa/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 + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, "engines": { - "node": ">=8.0.0" + "node": ">= 0.8.0" } }, - "node_modules/fork-ts-checker-webpack-plugin": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", - "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, "dependencies": { - "@babel/code-frame": "^7.16.7", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "cosmiconfig": "^7.0.1", - "deepmerge": "^4.2.2", - "fs-extra": "^10.0.0", - "memfs": "^3.4.1", - "minimatch": "^3.0.4", - "node-abort-controller": "^3.0.1", - "schema-utils": "^3.1.1", - "semver": "^7.3.5", - "tapable": "^2.2.1" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=12.13.0", - "yarn": ">=1.0.0" - }, - "peerDependencies": { - "typescript": ">3.6.0", - "webpack": "^5.11.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/form-data": { - "version": "4.0.0", - "license": "MIT", + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.10.0" } }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.17" - } + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "license": "MIT", + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dependencies": { - "fetch-blob": "^3.1.2" + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" }, "engines": { - "node": ">=12.20.0" + "node": ">=4" } }, - "node_modules/formidable": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", + "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==" + }, + "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, "dependencies": { - "dezalgo": "^1.0.4", - "hexoid": "^1.0.0", - "once": "^1.4.0", - "qs": "^6.11.0" + "@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" }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8.6.0" } }, - "node_modules/fresh": { - "version": "0.5.2", - "license": "MIT", - "engines": { - "node": ">= 0.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==" }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "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 + }, + "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==" + }, + "node_modules/fast-xml-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", + "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "strnum": "^1.0.5" }, - "engines": { - "node": ">=12" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", + "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": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "reusify": "^1.0.4" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "dependencies": { + "bser": "2.1.1" } }, - "node_modules/generic-pool": { - "version": "3.9.0", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 4" + "node_modules/fengari": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.4.tgz", + "integrity": "sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==", + "dev": true, + "dependencies": { + "readline-sync": "^1.4.9", + "sprintf-js": "^1.1.1", + "tmp": "^0.0.33" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "node_modules/fengari-interop": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/fengari-interop/-/fengari-interop-0.1.3.tgz", + "integrity": "sha512-EtZ+oTu3kEwVJnoymFPBVLIbQcCoy9uWCVnMA6h3M/RqHkUBsLYp29+RRHf9rKr6GwjubWREU1O7RretFIXjHw==", + "dev": true, + "peerDependencies": { + "fengari": "^0.1.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "license": "ISC", + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": "^12.20 || >= 14.13" } }, - "node_modules/get-intrinsic": { - "version": "1.2.1", - "license": "MIT", + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3" + "escape-string-regexp": "^1.0.5" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-stdin": { - "version": "5.0.1", - "dev": true, - "license": "MIT", + "node_modules/figures/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==", "engines": { - "node": ">=0.12.0" + "node": ">=0.8.0" } }, - "node_modules/get-stream": { + "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", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/get-tsconfig": { - "version": "4.6.0", - "dev": true, - "license": "MIT", + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "minimatch": "^5.0.1" } }, - "node_modules/glob": { - "version": "7.2.3", - "license": "ISC", + "node_modules/filelist/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==", "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" + "balanced-match": "^1.0.0" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dependencies": { - "is-glob": "^4.0.3" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=10.13.0" + "node": ">=10" } }, - "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==" - }, - "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, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dependencies": { - "type-fest": "^0.20.2" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/globalthis": { - "version": "1.0.3", - "dev": true, - "license": "MIT", + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { - "define-properties": "^1.1.3" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "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.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8" } }, - "node_modules/globby": { - "version": "11.1.0", + "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": { - "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" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=10" @@ -6958,489 +7746,437 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.0.1", + "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", "dependencies": { - "get-intrinsic": "^1.1.3" + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/got": { - "version": "12.6.1", + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "license": "MIT", "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" + "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": ">=14.16" + "node": "*" }, "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/handlebars": { - "version": "4.7.7", + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, - "license": "MIT", "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" + "glob": "^7.1.3" }, "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" + "rimraf": "bin.js" }, - "optionalDependencies": { - "uglify-js": "^3.1.4" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/hard-rejection": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "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 + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "engines": { - "node": ">=6" + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/has": { - "version": "1.0.3", - "license": "MIT", + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", "dependencies": { - "function-bind": "^1.1.1" + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "dev": true, - "license": "MIT", + "node": ">=14" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "license": "MIT", - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/has-own-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", - "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", + "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^8.2.0", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, "engines": { - "node": ">=8" + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "dev": true, - "license": "MIT", + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { - "get-intrinsic": "^1.1.1" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.1", - "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 6" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "license": "MIT", + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 14.17" } }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "dependencies": { - "has-symbols": "^1.0.2" + "fetch-blob": "^3.1.2" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12.20.0" } }, - "node_modules/haxec": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/haxec/-/haxec-2.0.1.tgz", - "integrity": "sha512-2DaSqGZIzgVkZ4YFHbk9Su0Q6gm7YbzNX9njOHK/D/XklOdvgTemsPmjcyExlLdkl7lRlNIW0Wxo6niVfpWedw==", + "node_modules/formidable": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", + "integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==", "dev": true, "dependencies": { - "foreground-child": "^2.0.0", - "spawn-wrap": "^2.0.0" + "dezalgo": "^1.0.4", + "hexoid": "^1.0.0", + "once": "^1.4.0" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/hexoid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", - "dev": true, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/highlight.js": { - "version": "10.7.3", - "license": "BSD-3-Clause", - "peer": true, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { - "node": "*" + "node": ">= 0.6" } }, - "node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dependencies": { - "lru-cache": "^6.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "dev": true, - "license": "BSD-2-Clause" + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==" }, - "node_modules/http-errors": { - "version": "2.0.0", - "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/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/http2-wrapper": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10.19.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/human-signals": { - "version": "4.3.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14.18.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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, + "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, "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "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.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "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": ">= 4" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "license": "MIT", + "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==", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "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": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/import-local": { - "version": "3.1.0", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "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": ">=8.0.0" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "license": "MIT", + "node_modules/get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==", + "dev": true, "engines": { - "node": ">=0.8.19" + "node": ">=0.12.0" } }, - "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==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inherits": { - "version": "2.0.4", - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "dev": true, - "license": "ISC" - }, - "node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=12.0.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/inquirer/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "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==", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/int53": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/int53/-/int53-0.2.4.tgz", - "integrity": "sha512-a5jlKftS7HUOhkUyYD7j2sJ/ZnvWiNlZS1ldR+g1ifQ+/UuZXIE+YTc/lK1qGj/GwAU5F8Z0e1eVq2t1J5Ob2g==" + "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==" }, - "node_modules/interface-ipld-format": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/interface-ipld-format/-/interface-ipld-format-1.0.1.tgz", - "integrity": "sha512-WV/ar+KQJVoQpqRDYdo7YPGYIUHJxCuOEhdvsRpzLqoOIVCqPKdMMYmsLL1nCRsF3yYNio+PAJbCKiv6drrEAg==", - "deprecated": "This module has been superseded by the multiformats module", + "node_modules/glob/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==", "dependencies": { - "cids": "^1.1.6", - "multicodec": "^3.0.1", - "multihashes": "^4.0.2" + "balanced-match": "^1.0.0" } }, - "node_modules/internal-slot": { - "version": "1.0.5", - "dev": true, - "license": "MIT", + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 0.4" - } - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "engines": { - "node": ">= 0.10" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ioredis": { - "version": "5.3.2", - "license": "MIT", + "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, "dependencies": { - "@ioredis/commands": "^1.1.1", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" + "type-fest": "^0.20.2" }, "engines": { - "node": ">=12.22.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ioredis-mock": { - "version": "8.8.3", - "resolved": "https://registry.npmjs.org/ioredis-mock/-/ioredis-mock-8.8.3.tgz", - "integrity": "sha512-LkF17WIyYkPfUhvp0fjIZ+HKhILEoq1J2b71vv+9EOW054UlkySVEvgQ2RolXM+eI759MteHtXQvv0oRn0lkUg==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { - "@ioredis/as-callback": "^3.0.0", - "@ioredis/commands": "^1.2.0", - "fengari": "^0.1.4", - "fengari-interop": "^0.1.3", - "semver": "^7.5.4" + "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": ">=12.22" + "node": ">=10" }, - "peerDependencies": { - "@types/ioredis-mock": "^8", - "ioredis": "^5" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "license": "MIT", - "engines": { - "node": ">= 0.10" + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ipfs-only-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ipfs-only-hash/-/ipfs-only-hash-4.0.0.tgz", - "integrity": "sha512-TE1DZCvfw8i3gcsTq3P4TFx3cKFJ3sluu/J3XINkJhIN9OwJgNMqKA+WnKx6ByCb1IoPXsTp1KM7tupElb6SyA==", + "node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "dev": true, "dependencies": { - "ipfs-unixfs-importer": "^7.0.1", - "meow": "^9.0.0" + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" }, - "bin": { - "ipfs-only-hash": "cli.js" + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/ipfs-only-hash/node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } + "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==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, - "node_modules/ipfs-only-hash/node_modules/hamt-sharding": { + "node_modules/hamt-sharding": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/hamt-sharding/-/hamt-sharding-2.0.1.tgz", "integrity": "sha512-vnjrmdXG9dDs1m/H4iJ6z0JFI2NtgsW5keRkTcM85NGak69Mkf5PHUqBz+Xs0T4sg0ppvj9O5EGAJo40FTxmmA==", @@ -7453,7 +8189,7 @@ "npm": ">=6.0.0" } }, - "node_modules/ipfs-only-hash/node_modules/hamt-sharding/node_modules/uint8arrays": { + "node_modules/hamt-sharding/node_modules/uint8arrays": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", @@ -7461,504 +8197,560 @@ "multiformats": "^9.4.2" } }, - "node_modules/ipfs-only-hash/node_modules/ipfs-unixfs": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-4.0.3.tgz", - "integrity": "sha512-hzJ3X4vlKT8FQ3Xc4M1szaFVjsc1ZydN+E4VQ91aXxfpjFn9G2wsMo1EFdAXNq/BUnN5dgqIOMP5zRYr3DTsAw==", - "dependencies": { - "err-code": "^3.0.1", - "protobufjs": "^6.10.2" - }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "engines": { - "node": ">=14.0.0", - "npm": ">=7.0.0" + "node": ">=6" } }, - "node_modules/ipfs-only-hash/node_modules/ipfs-unixfs-importer": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-7.0.3.tgz", - "integrity": "sha512-qeFOlD3AQtGzr90sr5Tq1Bi8pT5Nr2tSI8z310m7R4JDYgZc6J1PEZO3XZQ8l1kuGoqlAppBZuOYmPEqaHcVQQ==", - "dependencies": { - "bl": "^5.0.0", - "cids": "^1.1.5", - "err-code": "^3.0.1", - "hamt-sharding": "^2.0.0", - "ipfs-unixfs": "^4.0.3", - "ipld-dag-pb": "^0.22.2", - "it-all": "^1.0.5", - "it-batch": "^1.0.8", - "it-first": "^1.0.6", - "it-parallel-batch": "^1.0.9", - "merge-options": "^3.0.4", - "multihashing-async": "^2.1.0", - "rabin-wasm": "^0.1.4", - "uint8arrays": "^2.1.2" - }, + "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==", "engines": { - "node": ">=14.0.0", - "npm": ">=7.0.0" + "node": ">=8" } }, - "node_modules/ipfs-only-hash/node_modules/it-all": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", - "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==" - }, - "node_modules/ipfs-only-hash/node_modules/it-batch": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-1.0.9.tgz", - "integrity": "sha512-7Q7HXewMhNFltTsAMdSz6luNhyhkhEtGGbYek/8Xb/GiqYMtwUmopE1ocPSiJKKp3rM4Dt045sNFoUu+KZGNyA==" - }, - "node_modules/ipfs-only-hash/node_modules/it-first": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/it-first/-/it-first-1.0.7.tgz", - "integrity": "sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==" + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "engines": { + "node": ">=8" + } }, - "node_modules/ipfs-only-hash/node_modules/it-parallel-batch": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-1.0.11.tgz", - "integrity": "sha512-UWsWHv/kqBpMRmyZJzlmZeoAMA0F3SZr08FBdbhtbe+MtoEBgr/ZUAKrnenhXCBrsopy76QjRH2K/V8kNdupbQ==", + "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==", "dependencies": { - "it-batch": "^1.0.9" + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ipfs-only-hash/node_modules/protobufjs": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", - "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" + "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==", + "engines": { + "node": ">= 0.4" }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ipfs-only-hash/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==", + "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==", + "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==", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" } }, - "node_modules/ipfs-only-hash/node_modules/uint8arrays": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.10.tgz", - "integrity": "sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==", - "dependencies": { - "multiformats": "^9.4.2" + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" } }, - "node_modules/ipld-dag-pb": { - "version": "0.22.3", - "resolved": "https://registry.npmjs.org/ipld-dag-pb/-/ipld-dag-pb-0.22.3.tgz", - "integrity": "sha512-dfG5C5OVAR4FEP7Al2CrHWvAyIM7UhAQrjnOYOIxXGQz5NlEj6wGX0XQf6Ru6or1na6upvV3NQfstapQG8X2rg==", - "deprecated": "This module has been superseded by @ipld/dag-pb and multiformats", + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dependencies": { - "cids": "^1.0.0", - "interface-ipld-format": "^1.0.0", - "multicodec": "^3.0.1", - "multihashing-async": "^2.0.0", - "protobufjs": "^6.10.2", - "stable": "^0.1.8", - "uint8arrays": "^2.0.5" + "lru-cache": "^6.0.0" }, "engines": { - "node": ">=6.0.0", - "npm": ">=3.0.0" + "node": ">=10" } }, - "node_modules/ipld-dag-pb/node_modules/protobufjs": { - "version": "6.11.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", - "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", - "hasInstallScript": true, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", - "@types/node": ">=13.7.0", - "long": "^4.0.0" + "yallist": "^4.0.0" }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "engines": { + "node": ">=10" } }, - "node_modules/ipld-dag-pb/node_modules/uint8arrays": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.10.tgz", - "integrity": "sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==", - "dependencies": { - "multiformats": "^9.4.2" - } + "node_modules/hosted-git-info/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/is-array-buffer": { - "version": "3.0.2", - "dev": true, - "license": "MIT", + "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 + }, + "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 + }, + "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==", "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.8" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "license": "MIT" - }, - "node_modules/is-bigint": { - "version": "1.0.4", + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", "dev": true, - "license": "MIT", "dependencies": { - "has-bigints": "^1.0.1" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=10.19.0" } }, - "node_modules/is-binary-path": { + "node_modules/human-signals": { "version": "2.1.0", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=10.17.0" } }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "dev": true, - "license": "MIT", + "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==", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-callable": { - "version": "1.2.7", + "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" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, - "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.12.1", - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 4" } }, - "node_modules/is-date-object": { - "version": "1.0.5", - "dev": true, - "license": "MIT", + "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==", "dependencies": { - "has-tostringtag": "^1.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-docker": { - "version": "3.0.0", + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, - "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, "bin": { - "is-docker": "cli.js" + "import-local-fixture": "fixtures/cli.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "license": "MIT", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.8.19" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "license": "MIT", + "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==", "engines": { "node": ">=8" } }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" + "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==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "license": "MIT", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", "dependencies": { - "is-extglob": "^2.1.1" + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=12.0.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "dev": true, - "license": "MIT", + "node_modules/int53": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/int53/-/int53-0.2.4.tgz", + "integrity": "sha512-a5jlKftS7HUOhkUyYD7j2sJ/ZnvWiNlZS1ldR+g1ifQ+/UuZXIE+YTc/lK1qGj/GwAU5F8Z0e1eVq2t1J5Ob2g==" + }, + "node_modules/interface-ipld-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/interface-ipld-format/-/interface-ipld-format-1.0.1.tgz", + "integrity": "sha512-WV/ar+KQJVoQpqRDYdo7YPGYIUHJxCuOEhdvsRpzLqoOIVCqPKdMMYmsLL1nCRsF3yYNio+PAJbCKiv6drrEAg==", + "deprecated": "This module has been superseded by the multiformats module", "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" + "cids": "^1.1.6", + "multicodec": "^3.0.1", + "multihashes": "^4.0.2" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ioredis": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" }, "engines": { - "node": ">=14.16" + "node": ">=12.22.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/ioredis" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "node_modules/ioredis-mock": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ioredis-mock/-/ioredis-mock-8.9.0.tgz", + "integrity": "sha512-yIglcCkI1lvhwJVoMsR51fotZVsPsSk07ecTCgRTRlicG0Vq3lke6aAaHklyjmRNRsdYAgswqC2A0bPtQK4LSw==", + "dev": true, + "dependencies": { + "@ioredis/as-callback": "^3.0.0", + "@ioredis/commands": "^1.2.0", + "fengari": "^0.1.4", + "fengari-interop": "^0.1.3", + "semver": "^7.5.4" + }, "engines": { - "node": ">=8" + "node": ">=12.22" + }, + "peerDependencies": { + "@types/ioredis-mock": "^8", + "ioredis": "^5" } }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "dev": true, - "license": "MIT", + "node_modules/ioredis/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">= 0.4" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/is-number": { - "version": "7.0.0", - "license": "MIT", + "node_modules/ioredis/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "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==", "engines": { - "node": ">=0.12.0" + "node": ">= 0.10" } }, - "node_modules/is-number-object": { - "version": "1.0.7", - "dev": true, - "license": "MIT", + "node_modules/ipfs-only-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ipfs-only-hash/-/ipfs-only-hash-4.0.0.tgz", + "integrity": "sha512-TE1DZCvfw8i3gcsTq3P4TFx3cKFJ3sluu/J3XINkJhIN9OwJgNMqKA+WnKx6ByCb1IoPXsTp1KM7tupElb6SyA==", "dependencies": { - "has-tostringtag": "^1.0.0" + "ipfs-unixfs-importer": "^7.0.1", + "meow": "^9.0.0" }, - "engines": { - "node": ">= 0.4" + "bin": { + "ipfs-only-hash": "cli.js" + } + }, + "node_modules/ipfs-unixfs": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/ipfs-unixfs/-/ipfs-unixfs-4.0.3.tgz", + "integrity": "sha512-hzJ3X4vlKT8FQ3Xc4M1szaFVjsc1ZydN+E4VQ91aXxfpjFn9G2wsMo1EFdAXNq/BUnN5dgqIOMP5zRYr3DTsAw==", + "dependencies": { + "err-code": "^3.0.1", + "protobufjs": "^6.10.2" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "dev": true, - "license": "MIT", + "node_modules/ipfs-unixfs-importer": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ipfs-unixfs-importer/-/ipfs-unixfs-importer-7.0.3.tgz", + "integrity": "sha512-qeFOlD3AQtGzr90sr5Tq1Bi8pT5Nr2tSI8z310m7R4JDYgZc6J1PEZO3XZQ8l1kuGoqlAppBZuOYmPEqaHcVQQ==", + "dependencies": { + "bl": "^5.0.0", + "cids": "^1.1.5", + "err-code": "^3.0.1", + "hamt-sharding": "^2.0.0", + "ipfs-unixfs": "^4.0.3", + "ipld-dag-pb": "^0.22.2", + "it-all": "^1.0.5", + "it-batch": "^1.0.8", + "it-first": "^1.0.6", + "it-parallel-batch": "^1.0.9", + "merge-options": "^3.0.4", + "multihashing-async": "^2.1.0", + "rabin-wasm": "^0.1.4", + "uint8arrays": "^2.1.2" + }, "engines": { - "node": ">=8" + "node": ">=14.0.0", + "npm": ">=7.0.0" } }, - "node_modules/is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "node_modules/ipld-dag-pb": { + "version": "0.22.3", + "resolved": "https://registry.npmjs.org/ipld-dag-pb/-/ipld-dag-pb-0.22.3.tgz", + "integrity": "sha512-dfG5C5OVAR4FEP7Al2CrHWvAyIM7UhAQrjnOYOIxXGQz5NlEj6wGX0XQf6Ru6or1na6upvV3NQfstapQG8X2rg==", + "deprecated": "This module has been superseded by @ipld/dag-pb and multiformats", + "dependencies": { + "cids": "^1.0.0", + "interface-ipld-format": "^1.0.0", + "multicodec": "^3.0.1", + "multihashing-async": "^2.0.0", + "protobufjs": "^6.10.2", + "stable": "^0.1.8", + "uint8arrays": "^2.0.5" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0.0", + "npm": ">=3.0.0" } }, - "node_modules/is-regex": { - "version": "1.1.4", - "dev": true, - "license": "MIT", + "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==" + }, + "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==", "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "dev": true, - "license": "MIT", + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "call-bind": "^1.0.2" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "dev": true, - "license": "MIT", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/is-string": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-symbol": { - "version": "1.0.4", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=6" } }, - "node_modules/is-typed-array": { - "version": "1.1.10", - "dev": true, - "license": "MIT", + "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==", "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/is-weakref": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "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==", + "engines": { + "node": ">=0.12.0" } }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "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, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "engines": { "node": ">=8" } }, - "node_modules/is-wsl/node_modules/is-docker": { - "version": "2.2.1", + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, "engines": { "node": ">=8" }, @@ -7966,13 +8758,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isexe": { "version": "2.0.0", - "license": "ISC" + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isomorphic-ws": { "version": "4.0.1", @@ -7983,50 +8788,49 @@ } }, "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "license": "BSD-3-Clause", + "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, "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "license": "BSD-3-Clause", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz", + "integrity": "sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==", + "dev": true, "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "semver": "^7.5.4" }, "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">=10" } }, "node_modules/istanbul-lib-report": { - "version": "3.0.0", + "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": "^3.0.0", + "make-dir": "^4.0.0", "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -8036,10 +8840,43 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/istanbul-lib-source-maps/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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/istanbul-reports": { - "version": "3.1.5", + "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" @@ -8048,17 +8885,58 @@ "node": ">=8" } }, + "node_modules/it-all": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/it-all/-/it-all-1.0.6.tgz", + "integrity": "sha512-3cmCc6Heqe3uWi3CVM/k51fa/XbMFpQVzFoDsV0IZNHSQDyAXl3c4MjHkFX5kF3922OGj7Myv1nSEUgRtcuM1A==" + }, + "node_modules/it-batch": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/it-batch/-/it-batch-1.0.9.tgz", + "integrity": "sha512-7Q7HXewMhNFltTsAMdSz6luNhyhkhEtGGbYek/8Xb/GiqYMtwUmopE1ocPSiJKKp3rM4Dt045sNFoUu+KZGNyA==" + }, + "node_modules/it-first": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/it-first/-/it-first-1.0.7.tgz", + "integrity": "sha512-nvJKZoBpZD/6Rtde6FXqwDqDZGF1sCADmr2Zoc0hZsIvnE449gRFnGctxDf09Bzc/FWnHXAdaHVIetY6lrE0/g==" + }, + "node_modules/it-parallel-batch": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/it-parallel-batch/-/it-parallel-batch-1.0.11.tgz", + "integrity": "sha512-UWsWHv/kqBpMRmyZJzlmZeoAMA0F3SZr08FBdbhtbe+MtoEBgr/ZUAKrnenhXCBrsopy76QjRH2K/V8kNdupbQ==", + "dependencies": { + "it-batch": "^1.0.9" + } + }, "node_modules/iterare": { "version": "1.2.1", - "license": "ISC", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", "engines": { "node": ">=6" } }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { - "version": "10.8.7", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", - "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -8073,14 +8951,15 @@ } }, "node_modules/jest": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.5.0" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -8098,121 +8977,42 @@ } }, "node_modules/jest-changed-files": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-changed-files/node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/jest-changed-files/node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/jest-changed-files/node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/mimic-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/jest-changed-files/node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-changed-files/node_modules/onetime": { - "version": "5.1.2", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, - "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-changed-files/node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-circus": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", - "dedent": "^0.7.0", + "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -8222,21 +9022,21 @@ } }, "node_modules/jest-cli": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -8255,30 +9055,31 @@ } }, "node_modules/jest-config": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, - "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -8298,23 +9099,46 @@ } } }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": 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/jest-diff": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, "dependencies": { "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.4.3", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, - "license": "MIT", "dependencies": { "detect-newline": "^3.0.0" }, @@ -8323,56 +9147,62 @@ } }, "node_modules/jest-each": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-get-type": { - "version": "29.4.3", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -8384,41 +9214,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, - "license": "MIT", "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -8427,12 +9262,14 @@ } }, "node_modules/jest-mock": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.5.0" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8440,8 +9277,9 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" }, @@ -8455,23 +9293,26 @@ } }, "node_modules/jest-regex-util": { - "version": "29.4.3", - "license": "MIT", + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, - "license": "MIT", "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -8481,41 +9322,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, - "license": "MIT", "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -8523,40 +9366,51 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jest-runner/node_modules/source-map-support": { "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, - "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "node_modules/jest-runtime": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -8564,51 +9418,64 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-runtime/node_modules/strip-bom": { - "version": "4.0.0", + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "license": "MIT", + "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": ">=8" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/jest-snapshot": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.5.0", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" + "pretty-format": "^29.7.0", + "semver": "^7.5.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-util": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", @@ -8619,17 +9486,30 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/jest-validate": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/types": "^29.5.0", + "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", + "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.5.0" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -8637,8 +9517,9 @@ }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -8647,17 +9528,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.5.0", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, - "license": "MIT", "dependencies": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -8665,11 +9547,13 @@ } }, "node_modules/jest-worker": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, "dependencies": { "@types/node": "*", - "jest-util": "^29.5.0", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -8679,7 +9563,9 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", - "license": "MIT", + "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" }, @@ -8691,12 +9577,13 @@ } }, "node_modules/joi": { - "version": "17.9.2", - "license": "BSD-3-Clause", + "version": "17.13.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", + "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } @@ -8713,7 +9600,8 @@ }, "node_modules/js-yaml": { "version": "4.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": { "argparse": "^2.0.1" }, @@ -8723,7 +9611,9 @@ }, "node_modules/jsesc": { "version": "2.5.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -8733,21 +9623,25 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "license": "MIT" + "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==" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "license": "MIT" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "dev": true, - "license": "MIT" + "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 }, "node_modules/json-stringify-pretty-compact": { "version": "4.0.0", @@ -8756,11 +9650,13 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "license": "ISC" + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "node_modules/json5": { "version": "2.2.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, @@ -8785,9 +9681,10 @@ } }, "node_modules/keyv": { - "version": "4.5.2", + "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", "dependencies": { "json-buffer": "3.0.1" } @@ -8802,24 +9699,27 @@ }, "node_modules/kleur": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/leven": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, - "license": "MIT", "engines": { "node": ">=6" } }, "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", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -8829,20 +9729,22 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.10.34", - "license": "MIT" + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.1.tgz", + "integrity": "sha512-Wze1LPwcnzvcKGcRHFGFECTaLzxOtujwpf924difr5zniyYv1C2PiW0419qDR7m8lKDxsImu5mwxFuXhXpjmvw==" }, "node_modules/license-report": { - "version": "6.4.0", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/license-report/-/license-report-6.5.0.tgz", + "integrity": "sha512-e8VbNeBb2UumBaTCciINTmW0MquM9HmSSGskCxFqIPjsypYHWlwoz5k6ydGP1lk5GaYUHBZsN+XoENJ5C9c04A==", "dev": true, - "license": "MIT", "dependencies": { "@kessler/tableify": "^1.0.2", "debug": "^4.3.4", "eol": "^0.9.1", - "got": "^12.6.0", + "got": "^13.0.0", "rc": "^1.2.8", - "semver": "^7.3.8", + "semver": "^7.5.4", "tablemark": "^3.0.0", "text-table": "^0.2.0", "visit-values": "^2.0.0" @@ -8851,12 +9753,36 @@ "license-report": "index.js" }, "engines": { - "node": ">=14" + "node": ">=16" + } + }, + "node_modules/license-report/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/license-report/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/lines-and-columns": { "version": "1.2.4", - "license": "MIT" + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/loader-runner": { "version": "4.3.0", @@ -8868,8 +9794,9 @@ }, "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" }, @@ -8882,31 +9809,30 @@ }, "node_modules/lodash": { "version": "4.17.21", - "license": "MIT" - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "license": "MIT", - "optional": true, - "peer": true + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.defaults": { "version": "4.2.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, "node_modules/lodash.isarguments": { "version": "3.1.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true }, "node_modules/lodash.merge": { "version": "4.6.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -8930,16 +9856,18 @@ }, "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/lowercase-keys": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", "dev": true, - "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -8948,37 +9876,26 @@ } }, "node_modules/lru-cache": { - "version": "6.0.0", - "license": "ISC", + "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, "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "yallist": "^3.0.2" } }, "node_modules/luxon": { - "version": "3.3.0", - "license": "MIT", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", "engines": { "node": ">=12" } }, - "node_modules/macos-release": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", - "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/magic-string": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", - "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "version": "0.30.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", + "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -8987,34 +9904,30 @@ } }, "node_modules/make-dir": { - "version": "3.1.0", + "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": "^6.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/make-error": { "version": "1.3.6", - "license": "ISC" + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "node_modules/makeerror": { "version": "1.0.12", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, "dependencies": { "tmpl": "1.0.5" } @@ -9032,7 +9945,8 @@ }, "node_modules/media-typer": { "version": "0.3.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { "node": ">= 0.6" } @@ -9094,7 +10008,8 @@ }, "node_modules/merge-descriptors": { "version": "1.0.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, "node_modules/merge-options": { "version": "3.0.4", @@ -9107,36 +10022,33 @@ "node": ">=10" } }, - "node_modules/merge-options/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "engines": { - "node": ">=8" - } - }, "node_modules/merge-stream": { "version": "2.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "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", "engines": { "node": ">= 8" } }, "node_modules/methods": { "version": "1.1.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "engines": { "node": ">= 0.6" } }, "node_modules/micromatch": { "version": "4.0.5", - "license": "MIT", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -9145,9 +10057,22 @@ "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, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "1.6.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "bin": { "mime": "cli.js" }, @@ -9157,14 +10082,16 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { "version": "2.1.35", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { "mime-db": "1.52.0" }, @@ -9173,20 +10100,18 @@ } }, "node_modules/mimic-fn": { - "version": "4.0.0", - "dev": true, - "license": "MIT", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/mimic-response": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true, - "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -9204,7 +10129,8 @@ }, "node_modules/minimatch": { "version": "3.1.2", - "license": "ISC", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -9214,7 +10140,8 @@ }, "node_modules/minimist": { "version": "1.2.8", - "license": "MIT", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9232,17 +10159,26 @@ "node": ">= 6" } }, + "node_modules/minimist-options/node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", + "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/mkdirp": { "version": "0.5.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dependencies": { "minimist": "^1.2.6" }, @@ -9251,27 +10187,31 @@ } }, "node_modules/mock-socket": { - "version": "9.2.1", - "license": "MIT", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", + "integrity": "sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==", "engines": { "node": ">= 8" } }, "node_modules/ms": { - "version": "2.1.2", - "license": "MIT" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/msgpackr": { - "version": "1.9.5", - "license": "MIT", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.2.tgz", + "integrity": "sha512-L60rsPynBvNE+8BWipKKZ9jHcSGbtyJYIwjRq0VrIvQ08cRjntGXJYW/tmciZ2IHWIY8WEW32Qa2xbh5+SKBZA==", "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "node_modules/msgpackr-extract": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz", + "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==", "hasInstallScript": true, - "license": "MIT", "optional": true, "dependencies": { "node-gyp-build-optional-packages": "5.0.7" @@ -9290,7 +10230,8 @@ }, "node_modules/multer": { "version": "1.4.4-lts.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4-lts.1.tgz", + "integrity": "sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.0.0", @@ -9405,44 +10346,39 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, - "node_modules/mz": { - "version": "2.7.0", - "license": "MIT", - "peer": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "node_modules/negotiator": { "version": "0.6.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } }, "node_modules/neo-async": { "version": "2.6.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "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/nock": { - "version": "13.3.8", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.8.tgz", - "integrity": "sha512-96yVFal0c/W1lG7mmfRe7eO+hovrhJYd2obzzOZ90f6fjpeU/XNvd9cYHZKZAQJumDfhXgoTpkpJ9pvMj+hqHw==", + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", + "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", @@ -9452,6 +10388,27 @@ "node": ">= 10.13" } }, + "node_modules/nock/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nock/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -9459,6 +10416,8 @@ }, "node_modules/node-domexception": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "funding": [ { "type": "github", @@ -9469,7 +10428,6 @@ "url": "https://paypal.me/jimmywarting" } ], - "license": "MIT", "engines": { "node": ">=10.5.0" } @@ -9483,24 +10441,28 @@ } }, "node_modules/node-fetch": { - "version": "3.3.1", - "license": "MIT", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" + "whatwg-url": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": "4.x || >=6.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, "node_modules/node-gyp-build-optional-packages": { "version": "5.0.7", - "license": "MIT", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", + "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", "optional": true, "bin": { "node-gyp-build-optional-packages": "bin.js", @@ -9510,11 +10472,13 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" }, "node_modules/node-releases": { - "version": "2.0.12", - "license": "MIT" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/normalize-package-data": { "version": "3.0.3", @@ -9523,127 +10487,65 @@ "dependencies": { "hosted-git-info": "^4.0.1", "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "8.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "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==", "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/object.assign": { - "version": "4.1.4", + "node_modules/normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, "engines": { - "node": ">= 0.4" + "node": ">=14.16" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/object.entries": { - "version": "1.1.6", + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "path-key": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=8" } }, - "node_modules/object.values": { - "version": "1.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, + "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==", "engines": { - "node": ">= 0.4" - }, + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/on-finished": { "version": "2.4.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -9653,53 +10555,38 @@ }, "node_modules/once": { "version": "1.4.0", - "license": "ISC", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dependencies": { "wrappy": "1" } }, "node_modules/onetime": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "9.1.0", - "dev": true, - "license": "MIT", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dependencies": { - "default-browser": "^4.0.0", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^2.2.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=14.16" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/optionator": { - "version": "0.9.3", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -9727,19 +10614,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-name": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", - "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", + "node_modules/ora/node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dependencies": { - "macos-release": "^2.5.0", - "windows-release": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, "node_modules/os-tmpdir": { @@ -9752,16 +10634,18 @@ }, "node_modules/p-cancelable": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", "dev": true, - "license": "MIT", "engines": { "node": ">=12.20" } }, "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" }, @@ -9774,8 +10658,9 @@ }, "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" }, @@ -9788,19 +10673,21 @@ }, "node_modules/p-try": { "version": "2.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "engines": { "node": ">=6" } }, "node_modules/pako": { - "version": "2.1.0", - "license": "(MIT AND Zlib)", - "optional": true + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "node_modules/parent-module": { "version": "1.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dependencies": { "callsites": "^3.0.0" }, @@ -9810,7 +10697,8 @@ }, "node_modules/parse-json": { "version": "5.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -9824,123 +10712,109 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse5": { - "version": "5.1.1", - "license": "MIT", - "peer": true - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "license": "MIT", - "peer": true, - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "license": "MIT", - "peer": true - }, "node_modules/parseurl": { "version": "1.3.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "engines": { "node": ">= 0.8" } }, "node_modules/path-exists": { "version": "4.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "engines": { "node": ">=8" } }, "node_modules/path-is-absolute": { "version": "1.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "engines": { "node": ">=0.10.0" } }, "node_modules/path-key": { "version": "3.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "engines": { "node": ">=8" } }, "node_modules/path-parse": { "version": "1.0.7", - "license": "MIT" + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.1.tgz", - "integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", "engines": { "node": "14 || >=16.14" } }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.3.tgz", - "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/path-to-regexp": { "version": "3.2.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", + "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" }, "node_modules/path-type": { "version": "4.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "engines": { "node": ">=8" } }, "node_modules/picocolors": { "version": "1.0.0", - "license": "ISC" + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { - "version": "2.3.1", - "license": "MIT", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", + "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", "engines": { - "node": ">=8.6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pirates": { - "version": "4.0.5", - "license": "MIT", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, "engines": { "node": ">= 6" } }, "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" }, @@ -9950,8 +10824,9 @@ }, "node_modules/pkg-dir/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" @@ -9962,8 +10837,9 @@ }, "node_modules/pkg-dir/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" }, @@ -9973,8 +10849,9 @@ }, "node_modules/pkg-dir/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" }, @@ -9987,8 +10864,9 @@ }, "node_modules/pkg-dir/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" }, @@ -10006,16 +10884,17 @@ }, "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", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", - "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -10027,22 +10906,13 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/pretty-format": { - "version": "29.5.0", - "license": "MIT", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, "dependencies": { - "@jest/schemas": "^29.4.3", + "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" }, @@ -10052,7 +10922,9 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "engines": { "node": ">=10" }, @@ -10062,12 +10934,14 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/prompts": { "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, - "license": "MIT", "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -10078,14 +10952,41 @@ }, "node_modules/propagate": { "version": "2.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "engines": { "node": ">= 8" } }, + "node_modules/protobufjs": { + "version": "6.11.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz", + "integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": ">=13.7.0", + "long": "^4.0.0" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", - "license": "MIT", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -10096,26 +10997,21 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/punycode": { - "version": "2.3.0", - "license": "MIT", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } }, "node_modules/pure-rand": { - "version": "6.0.2", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -10126,8 +11022,7 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ], - "license": "MIT" + ] }, "node_modules/q": { "version": "1.5.1", @@ -10140,7 +11035,8 @@ }, "node_modules/qs": { "version": "6.11.0", - "license": "BSD-3-Clause", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dependencies": { "side-channel": "^1.0.4" }, @@ -10153,6 +11049,8 @@ }, "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": [ { @@ -10167,13 +11065,13 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/quick-lru": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" }, @@ -10197,47 +11095,26 @@ "rabin-wasm": "cli/bin.js" } }, - "node_modules/rabin-wasm/node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/rabin-wasm/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/rabin-wasm/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dependencies": { - "whatwg-url": "^5.0.0" + "ms": "2.1.2" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">=6.0" }, "peerDependenciesMeta": { - "encoding": { + "supports-color": { "optional": true } } }, - "node_modules/rabin-wasm/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==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } + "node_modules/rabin-wasm/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/randombytes": { "version": "2.1.0", @@ -10249,14 +11126,16 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { "version": "2.5.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -10269,8 +11148,9 @@ }, "node_modules/rc": { "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -10283,15 +11163,18 @@ }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-is": { - "version": "18.2.0", - "license": "MIT" + "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==", + "dev": true }, "node_modules/read-pkg": { "version": "5.2.0", @@ -10412,25 +11295,22 @@ } }, "node_modules/readable-stream": { - "version": "2.3.8", - "license": "MIT", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "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" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" - }, "node_modules/readdirp": { "version": "3.6.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dependencies": { "picomatch": "^2.2.1" }, @@ -10438,6 +11318,17 @@ "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==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/readline-sync": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", @@ -10470,26 +11361,10 @@ "node": ">=8" } }, - "node_modules/redis": { - "version": "4.6.7", - "license": "MIT", - "optional": true, - "peer": true, - "workspaces": [ - "./packages/*" - ], - "dependencies": { - "@redis/bloom": "1.2.0", - "@redis/client": "1.5.8", - "@redis/graph": "1.1.0", - "@redis/json": "1.0.4", - "@redis/search": "1.1.3", - "@redis/time-series": "1.0.4" - } - }, "node_modules/redis-errors": { "version": "1.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", "engines": { "node": ">=4" } @@ -10504,7 +11379,8 @@ }, "node_modules/redis-parser": { "version": "3.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", "dependencies": { "redis-errors": "^1.0.0" }, @@ -10513,42 +11389,11 @@ } }, "node_modules/reflect-metadata": { - "version": "0.1.13", - "license": "Apache-2.0", - "peer": true - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "license": "MIT", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "peer": true }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpp": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -10559,7 +11404,9 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "license": "MIT", + "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" } @@ -10573,10 +11420,11 @@ } }, "node_modules/resolve": { - "version": "1.22.2", - "license": "MIT", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -10589,13 +11437,15 @@ }, "node_modules/resolve-alpn": { "version": "1.2.1", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true }, "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" }, @@ -10605,39 +11455,35 @@ }, "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", - "license": "MIT", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "engines": { "node": ">=4" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/resolve.exports": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true, - "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/responselike": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", "dev": true, - "license": "MIT", "dependencies": { "lowercase-keys": "^3.0.0" }, @@ -10660,145 +11506,83 @@ "node": ">=8" } }, - "node_modules/restore-cursor/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==", - "engines": { - "node": ">=6" - } - }, - "node_modules/restore-cursor/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "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==" }, "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", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, "node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", "dependencies": { - "glob": "^7.1.3" + "glob": "^9.2.0" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-applescript": { - "version": "5.0.0", - "dev": true, - "license": "MIT", + "node_modules/rimraf/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==", "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "balanced-match": "^1.0.0" } }, - "node_modules/run-applescript/node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", + "node_modules/rimraf/node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/run-applescript/node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/run-applescript/node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/run-applescript/node_modules/mimic-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/run-applescript/node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-applescript/node_modules/onetime": { - "version": "5.1.2", - "dev": true, - "license": "MIT", + "node_modules/rimraf/node_modules/minimatch": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", "dependencies": { - "mimic-fn": "^2.1.0" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-applescript/node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", + "node_modules/rimraf/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "engines": { - "node": ">=6" + "node": ">=8" } }, "node_modules/run-async": { @@ -10811,6 +11595,8 @@ }, "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": [ { @@ -10826,7 +11612,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -10841,6 +11626,8 @@ }, "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", @@ -10854,25 +11641,18 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + ] }, "node_modules/safer-buffer": { "version": "2.1.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/scale-ts": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/scale-ts/-/scale-ts-1.6.0.tgz", + "integrity": "sha512-Ja5VCjNZR8TGKhUumy9clVVxcDpM+YFjAnkMuwQy68Hixio3VRRvWdE3g8T/yC+HXA0ZDQl2TGyUmtmbcVl40Q==", + "optional": true }, "node_modules/schema-utils": { "version": "3.3.0", @@ -10891,13 +11671,38 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dependencies": { - "lru-cache": "^6.0.0" + "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/schema-utils/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==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/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==" + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "bin": { "semver": "bin/semver.js" }, @@ -10907,7 +11712,8 @@ }, "node_modules/send": { "version": "0.18.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -10927,25 +11733,16 @@ "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "license": "MIT" - }, "node_modules/send/node_modules/ms": { "version": "2.1.3", - "license": "MIT" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/sentence-case": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "dev": true, - "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -10953,16 +11750,17 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dependencies": { "randombytes": "^2.1.0" } }, "node_modules/serve-static": { "version": "1.15.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -10973,25 +11771,31 @@ "node": ">= 0.8.0" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "license": "ISC" - }, - "node_modules/sha.js": { - "version": "2.4.11", - "license": "(MIT AND BSD-3-Clause)", - "peer": true, + "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==", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.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.2" }, - "bin": { - "sha.js": "bin.js" + "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==" + }, "node_modules/shebang-command": { "version": "2.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -11001,7 +11805,8 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "engines": { "node": ">=8" } @@ -11022,40 +11827,74 @@ "node": ">=4" } }, + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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/side-channel": { - "version": "1.0.4", - "license": "MIT", + "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==", "dependencies": { - "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" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { - "version": "3.0.7", - "license": "ISC" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/sisteransi": { "version": "1.0.5", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true }, "node_modules/slash": { "version": "3.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "engines": { "node": ">=8" } }, "node_modules/smoldot": { - "version": "1.0.4", - "license": "GPL-3.0-or-later WITH Classpath-exception-2.0", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/smoldot/-/smoldot-2.0.22.tgz", + "integrity": "sha512-B50vRgTY6v3baYH6uCgL15tfaag5tcS2o/P5q1OiXcKGv1axZDfz2dzzMuIkVpyMR2ug11F6EAtQlmYBQd292g==", "optional": true, "dependencies": { - "pako": "^2.0.4", "ws": "^8.8.1" } }, @@ -11065,42 +11904,35 @@ "integrity": "sha512-YIK6I2lsH072UE0aOFxxY1dPDCS43I5ktqHpeAsuLNYWkE5pGxRGWfDM4/vSUfNzXjC1Ivzt3qx31PCLmc9yqg==" }, "node_modules/source-map": { - "version": "0.6.1", - "license": "BSD-3-Clause", + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, "node_modules/source-map-support": { "version": "0.5.21", - "license": "MIT", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "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==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sparse-array": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/sparse-array/-/sparse-array-1.3.2.tgz", "integrity": "sha512-ZT711fePGn3+kQyLuv1fpd3rNSkNF8vd5Kv2D+qnOANeyKs3fx6bUMGWRPvgTTcYV64QMqZKZwcuaQSP3AZ0tg==" }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -11111,9 +11943,9 @@ } }, "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" }, "node_modules/spdx-expression-parse": { "version": "3.0.1", @@ -11125,14 +11957,15 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", - "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==" + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==" }, "node_modules/split-text-to-chunks": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split-text-to-chunks/-/split-text-to-chunks-1.0.0.tgz", + "integrity": "sha512-HLtEwXK/T4l7QZSJ/kOSsZC0o5e2Xg3GzKKFxm0ZexJXw0Bo4CaEl39l7MCSRHk9EOOL5jT8JIDjmhTtcoe6lQ==", "dev": true, - "license": "MIT", "dependencies": { "get-stdin": "^5.0.1", "minimist": "^1.2.0" @@ -11142,8 +11975,10 @@ } }, "node_modules/sprintf-js": { - "version": "1.0.3", - "license": "BSD-3-Clause" + "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==", + "dev": true }, "node_modules/stable": { "version": "0.1.8", @@ -11153,7 +11988,9 @@ }, "node_modules/stack-utils": { "version": "2.0.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -11163,43 +12000,47 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "engines": { "node": ">=8" } }, "node_modules/standard-as-callback": { "version": "2.1.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" }, "node_modules/statuses": { "version": "2.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { "node": ">= 0.8" } }, "node_modules/streamsearch": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", "engines": { "node": ">=10.0.0" } }, "node_modules/string_decoder": { - "version": "1.1.1", - "license": "MIT", + "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==", "dependencies": { - "safe-buffer": "~5.1.0" + "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" - }, "node_modules/string-length": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "license": "MIT", "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -11210,7 +12051,8 @@ }, "node_modules/string-width": { "version": "4.2.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -11220,51 +12062,36 @@ "node": ">=8" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "dev": true, - "license": "MIT", + "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==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "dev": true, - "license": "MIT", + "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==", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/strip-ansi": { + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -11273,21 +12100,21 @@ } }, "node_modules/strip-bom": { - "version": "3.0.0", - "license": "MIT", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/strip-final-newline": { - "version": "3.0.0", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, - "license": "MIT", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/strip-indent": { @@ -11303,8 +12130,9 @@ }, "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", "engines": { "node": ">=8" }, @@ -11312,10 +12140,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/superagent": { - "version": "8.1.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz", - "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", "dev": true, "dependencies": { "component-emitter": "^1.3.0", @@ -11323,14 +12156,30 @@ "debug": "^4.3.4", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.0", - "formidable": "^2.1.2", + "formidable": "^3.5.1", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.11.0", - "semver": "^7.3.8" + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" }, "engines": { - "node": ">=6.4.0 <13 || >=14" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/superagent/node_modules/mime": { @@ -11345,22 +12194,29 @@ "node": ">=4.0.0" } }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, "node_modules/supertest": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", - "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", "dev": true, "dependencies": { "methods": "^1.1.2", - "superagent": "^8.0.5" + "superagent": "^9.0.1" }, "engines": { - "node": ">=6.4.0" + "node": ">=14.18.0" } }, "node_modules/supports-color": { "version": "7.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dependencies": { "has-flag": "^4.0.0" }, @@ -11370,7 +12226,8 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "engines": { "node": ">= 0.4" }, @@ -11379,9 +12236,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.3.1.tgz", - "integrity": "sha512-El78OvXp9zMasfPrshtkW1CRx8AugAKoZuGGOTW+8llJzOV1RtDJYqQRz/6+2OakjeWWnZuRlN2Qj1Y0ilux3w==" + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz", + "integrity": "sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A==" }, "node_modules/symbol-observable": { "version": "4.0.0", @@ -11391,25 +12248,11 @@ "node": ">=0.10" } }, - "node_modules/synckit": { - "version": "0.8.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, "node_modules/tablemark": { - "version": "3.0.0", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tablemark/-/tablemark-3.1.0.tgz", + "integrity": "sha512-IwO6f0SEzp1Z+zqz/7ANUmeEac4gaNlknWyj/S9aSg11wZmWYnLeyI/xXvEOU88BYUIf8y30y0wxB58xIKrVlQ==", "dev": true, - "license": "MIT", "dependencies": { "sentence-case": "^3.0.4", "split-text-to-chunks": "^1.0.0" @@ -11420,15 +12263,16 @@ }, "node_modules/tapable": { "version": "2.2.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "engines": { "node": ">=6" } }, "node_modules/terser": { - "version": "5.19.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", - "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", + "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -11443,15 +12287,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "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==", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -11475,15 +12319,6 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -11518,7 +12353,9 @@ }, "node_modules/test-exclude": { "version": "6.0.0", - "license": "ISC", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -11528,30 +12365,32 @@ "node": ">=8" } }, - "node_modules/text-table": { - "version": "0.2.0", + "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==", "dev": true, - "license": "MIT" - }, - "node_modules/thenify": { - "version": "3.3.1", - "license": "MIT", - "peer": true, "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "license": "MIT", - "peer": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" + "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": ">=0.8" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, + "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 + }, "node_modules/thrift": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/thrift/-/thrift-0.16.0.tgz", @@ -11582,18 +12421,8 @@ }, "node_modules/time-constants": { "version": "1.0.3", - "license": "ISC" - }, - "node_modules/titleize": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "resolved": "https://registry.npmjs.org/time-constants/-/time-constants-1.0.3.tgz", + "integrity": "sha512-cppe5TusAujrdnXmbUWuCe89oSjBJVyhndrceWvELg0WLmlv3/IRlo9cx8sBgRgmMmiC/D0NKspJg+tCc92Xug==" }, "node_modules/tmp": { "version": "0.0.33", @@ -11608,18 +12437,23 @@ }, "node_modules/tmpl": { "version": "1.0.5", - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true }, "node_modules/to-fast-properties": { "version": "2.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, "engines": { "node": ">=4" } }, "node_modules/to-regex-range": { "version": "5.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dependencies": { "is-number": "^7.0.0" }, @@ -11629,33 +12463,21 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "engines": { "node": ">=0.6" } }, "node_modules/tr46": { "version": "0.0.3", - "license": "MIT" - }, - "node_modules/trace-unhandled": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/trace-unhandled/-/trace-unhandled-2.0.1.tgz", - "integrity": "sha512-wOZbhBiNyuZTs0b/ADZFTiTDVVDsvKQj/RkVJTKefH6u9CowGDSR+H/3miaGUrYCCuzS0nVmIzpbIIm6lRF8gg==", - "dev": true, - "dependencies": { - "haxec": "^2.0.1" - }, - "bin": { - "trace-unhandled": "bin.js" - }, - "engines": { - "node": ">=10" - } + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/tree-kill": { "version": "1.2.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "bin": { "tree-kill": "cli.js" } @@ -11681,9 +12503,10 @@ } }, "node_modules/ts-jest": { - "version": "29.1.0", + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", "dev": true, - "license": "MIT", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -11691,14 +12514,14 @@ "json5": "^2.2.3", "lodash.memoize": "4.x", "make-error": "1.x", - "semver": "7.x", + "semver": "^7.5.3", "yargs-parser": "^21.0.1" }, "bin": { "ts-jest": "cli.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", @@ -11723,15 +12546,16 @@ } }, "node_modules/ts-loader": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", - "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^7.3.4" + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { "node": ">=12.0.0" @@ -11742,8 +12566,9 @@ } }, "node_modules/ts-node": { - "version": "10.9.1", - "license": "MIT", + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -11816,10 +12641,31 @@ } } }, + "node_modules/ts-node-dev/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": 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/ts-node-dev/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" }, @@ -11829,8 +12675,9 @@ }, "node_modules/ts-node-dev/node_modules/rimraf": { "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, - "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -11840,8 +12687,9 @@ }, "node_modules/tsconfig": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", "dev": true, - "license": "MIT", "dependencies": { "@types/strip-bom": "^3.0.0", "@types/strip-json-comments": "0.0.30", @@ -11851,7 +12699,8 @@ }, "node_modules/tsconfig-paths": { "version": "4.2.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", @@ -11874,236 +12723,91 @@ "node": ">=10.13.0" } }, - "node_modules/tsconfig/node_modules/strip-json-comments": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tslib": { - "version": "2.5.3", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "license": "MIT", + "node_modules/tsconfig-paths/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==", "engines": { "node": ">=4" } }, - "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==", + "node_modules/tsconfig/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, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" + "node": ">=4" } }, - "node_modules/typed-array-length": { - "version": "1.0.4", + "node_modules/tsconfig/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "license": "MIT" - }, - "node_modules/typeorm": { - "version": "0.3.16", - "license": "MIT", - "peer": true, - "dependencies": { - "@sqltools/formatter": "^1.2.5", - "app-root-path": "^3.1.0", - "buffer": "^6.0.3", - "chalk": "^4.1.2", - "cli-highlight": "^2.1.11", - "date-fns": "^2.29.3", - "debug": "^4.3.4", - "dotenv": "^16.0.3", - "glob": "^8.1.0", - "mkdirp": "^2.1.3", - "reflect-metadata": "^0.1.13", - "sha.js": "^2.4.11", - "tslib": "^2.5.0", - "uuid": "^9.0.0", - "yargs": "^17.6.2" - }, - "bin": { - "typeorm": "cli.js", - "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", - "typeorm-ts-node-esm": "cli-ts-node-esm.js" - }, "engines": { - "node": ">= 12.9.0" - }, - "funding": { - "url": "https://opencollective.com/typeorm" - }, - "peerDependencies": { - "@google-cloud/spanner": "^5.18.0", - "@sap/hana-client": "^2.12.25", - "better-sqlite3": "^7.1.2 || ^8.0.0", - "hdb-pool": "^0.1.6", - "ioredis": "^5.0.4", - "mongodb": "^5.2.0", - "mssql": "^9.1.1", - "mysql2": "^2.2.5 || ^3.0.1", - "oracledb": "^5.1.0", - "pg": "^8.5.1", - "pg-native": "^3.0.0", - "pg-query-stream": "^4.0.0", - "redis": "^3.1.1 || ^4.0.0", - "sql.js": "^1.4.0", - "sqlite3": "^5.0.3", - "ts-node": "^10.7.0", - "typeorm-aurora-data-api-driver": "^2.0.0" - }, - "peerDependenciesMeta": { - "@google-cloud/spanner": { - "optional": true - }, - "@sap/hana-client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "hdb-pool": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mssql": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "oracledb": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-native": { - "optional": true - }, - "pg-query-stream": { - "optional": true - }, - "redis": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "ts-node": { - "optional": true - }, - "typeorm-aurora-data-api-driver": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/typeorm/node_modules/brace-expansion": { - "version": "2.0.1", - "license": "MIT", - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0" - } + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, - "node_modules/typeorm/node_modules/glob": { - "version": "8.1.0", - "license": "ISC", - "peer": true, + "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, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.8.0" } }, - "node_modules/typeorm/node_modules/minimatch": { - "version": "5.1.6", - "license": "ISC", - "peer": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/typeorm/node_modules/mkdirp": { - "version": "2.1.6", - "license": "MIT", - "peer": true, - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, + "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, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "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==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12112,21 +12816,36 @@ "node": ">=14.17" } }, - "node_modules/uglify-js": { - "version": "3.17.4", + "node_modules/typescript-eslint": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.8.0.tgz", + "integrity": "sha512-sheFG+/D8N/L7gC3WT0Q8sB97Nm573Yfr+vZFzl/4nBdYcmviBPtwGSX9TJ7wpVg28ocerKVOt+k2eGmHzcgVA==", "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "bin": { - "uglifyjs": "bin/uglifyjs" + "dependencies": { + "@typescript-eslint/eslint-plugin": "7.8.0", + "@typescript-eslint/parser": "7.8.0", + "@typescript-eslint/utils": "7.8.0" }, "engines": { - "node": ">=0.8.0" + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/uid": { "version": "2.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", "dependencies": { "@lukeed/csprng": "^1.0.0" }, @@ -12134,45 +12853,39 @@ "node": ">=8" } }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "dev": true, - "license": "MIT", + "node_modules/uint8arrays": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-2.1.10.tgz", + "integrity": "sha512-Q9/hhJa2836nQfEJSZTmr+pg9+cDJS9XEAp7N2Vg5MzL3bK/mkMVfjscRGYruP9jNda6MAdf4QD/y78gSzkp6A==", "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" + "multiformats": "^9.4.2" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "engines": { "node": ">= 10.0.0" } }, "node_modules/unpipe": { "version": "1.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "engines": { "node": ">= 0.8" } }, - "node_modules/untildify": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/update-browserslist-db": { - "version": "1.0.11", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz", + "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==", "funding": [ { "type": "opencollective", @@ -12187,9 +12900,8 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", "dependencies": { - "escalade": "^3.1.1", + "escalade": "^3.1.2", "picocolors": "^1.0.0" }, "bin": { @@ -12201,82 +12913,65 @@ }, "node_modules/upper-case-first": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "dev": true, - "license": "MIT", "dependencies": { "tslib": "^2.0.3" } }, "node_modules/uri-js": { "version": "4.4.1", - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dependencies": { "punycode": "^2.1.0" } }, "node_modules/util-deprecate": { "version": "1.0.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/utils-merge": { "version": "1.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", "dev": true, - "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "dev": true, - "license": "MIT" - }, "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", @@ -12287,8 +12982,9 @@ } }, "node_modules/validator": { - "version": "13.9.0", - "license": "MIT", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", "engines": { "node": ">= 0.10" } @@ -12300,32 +12996,31 @@ }, "node_modules/vary": { "version": "1.1.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "engines": { "node": ">= 0.8" } }, "node_modules/visit-values": { "version": "2.0.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/visit-values/-/visit-values-2.0.0.tgz", + "integrity": "sha512-vLFU70y3D915d611GnHYeHkEmq6ZZETzTH4P1hM6I9E3lBwH2VeBBEESe/bGCY+gAyK0qqLFn5bNFpui/GKmww==", + "dev": true }, "node_modules/walker": { "version": "1.0.8", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, "dependencies": { "makeerror": "1.0.12" } }, - "node_modules/wasm-brotli": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/wasm-brotli/-/wasm-brotli-2.0.2.tgz", - "integrity": "sha512-DgjRlpZz9z5br4TjQHSlDHRF9NIuGXHUj3AqO08koDCXz7EYzmPDb7T/6oar5UKLYgPp9Yxc2ImGpx4BMFwbNQ==" - }, "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -12343,44 +13038,47 @@ } }, "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "license": "MIT", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "engines": { "node": ">= 8" } }, "node_modules/webidl-conversions": { "version": "3.0.1", - "license": "BSD-2-Clause" + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.88.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.88.2.tgz", - "integrity": "sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==", + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@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-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.16.0", "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.9", + "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.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -12415,9 +13113,32 @@ "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==", + "peer": true, + "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==", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -12425,7 +13146,8 @@ }, "node_modules/which": { "version": "2.0.2", - "license": "ISC", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": { "isexe": "^2.0.0" }, @@ -12436,158 +13158,33 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "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-typed-array": { - "version": "1.1.9", + "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", - "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/windows-release": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", - "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", - "dependencies": { - "execa": "^4.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/windows-release/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/windows-release/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/windows-release/node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/windows-release/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/windows-release/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==", "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/windows-release/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dependencies": { - "path-key": "^3.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/windows-release/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/windows-release/node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "engines": { - "node": ">=6" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi": { + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -12602,11 +13199,14 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "license": "ISC" + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", - "license": "ISC", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -12615,9 +13215,16 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/write-file-atomic/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 + }, "node_modules/ws": { - "version": "8.13.0", - "license": "MIT", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", "engines": { "node": ">=10.0.0" }, @@ -12636,7 +13243,8 @@ }, "node_modules/xtend": { "version": "4.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "engines": { "node": ">=0.4" } @@ -12648,26 +13256,24 @@ }, "node_modules/y18n": { "version": "5.0.8", - "license": "ISC", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "engines": { "node": ">=10" } }, "node_modules/yallist": { - "version": "4.0.0", - "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==", - "engines": { - "node": ">= 6" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/yargs": { "version": "17.7.2", - "license": "MIT", + "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", @@ -12683,22 +13289,25 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "license": "ISC", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "engines": { "node": ">=12" } }, "node_modules/yn": { "version": "3.1.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "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", "engines": { "node": ">=10" }, diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 87533e97..a17fc822 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -38,84 +38,66 @@ }, "homepage": "https://github.com/AmplicaLabs/content-watcher-service#readme", "dependencies": { - "@bull-board/api": "^5.8.3", - "@bull-board/express": "^5.8.3", - "@bull-board/nestjs": "^5.8.3", - "@bull-board/ui": "^5.8.3", + "@bull-board/api": "^5.17.1", + "@bull-board/express": "^5.17.1", + "@bull-board/nestjs": "^5.17.1", + "@bull-board/ui": "^5.17.1", "@dsnp/activity-content": "^1.1.0", - "@dsnp/frequency-schemas": "^1.0.2", - "@dsnp/parquetjs": "^1.3.4", - "@frequency-chain/api-augment": "1.7.0", - "@jest/globals": "^29.5.0", - "@liaoliaots/nestjs-redis": "^9.0.5", + "@dsnp/frequency-schemas": "^1.1.0", + "@dsnp/parquetjs": "^1.6.2", + "@frequency-chain/api-augment": "1.11.1", "@multiformats/blake2": "^1.0.13", - "@nestjs/axios": "^2.0.0", - "@nestjs/bullmq": "^10.0.0", - "@nestjs/cli": "^10.1.14", - "@nestjs/common": "^9.4.0", - "@nestjs/config": "^2.3.1", - "@nestjs/core": "^9.4.0", - "@nestjs/event-emitter": "^1.4.1", - "@nestjs/platform-express": "^9.4.0", - "@nestjs/schedule": "^3.0.3", - "@nestjs/swagger": "^7.1.8", - "@nestjs/testing": "^9.4.0", - "@nestjs/typeorm": "^9.0.1", - "@polkadot/api": "^10.9.1", - "@polkadot/api-base": "^10.9.1", - "@polkadot/keyring": "^12.3.2", - "@polkadot/types": "^10.9.1", - "@polkadot/util": "^12.3.2", - "@polkadot/util-crypto": "^12.3.2", + "@nestjs/bullmq": "^10.1.1", + "@nestjs/cli": "^10.3.2", + "@nestjs/common": "^10.3.8", + "@nestjs/config": "^3.2.2", + "@nestjs/core": "^10.3.8", + "@nestjs/event-emitter": "^2.0.4", + "@nestjs/platform-express": "^10.3.8", + "@nestjs/schedule": "^4.0.2", + "@nestjs/swagger": "^7.3.1", + "@polkadot/api": "^10.12.4", + "@polkadot/api-base": "^10.12.4", + "@polkadot/types": "^10.12.4", + "@polkadot/util": "^12.6.2", + "@songkeys/nestjs-redis": "^10.0.0", "@types/multer": "^1.4.7", - "@types/uuid": "^9.0.2", - "axios": "^1.3.6", - "bullmq": "^3.0.0", + "axios": "^1.6.8", + "bullmq": "^5.7.8", "class-transformer": "^0.5.1", - "class-validator": "^0.14.0", + "class-validator": "^0.14.1", "form-data": "^4.0.0", - "ioredis": "^5.3.2", + "ioredis": "^5.4.1", "ipfs-only-hash": "^4.0.0", - "joi": "^17.9.1", + "joi": "^17.13.1", "mime-types": "^2.1.35", "multiformats": "9.9.0", "rxjs": "^7.8.1", "time-constants": "^1.0.3" }, "devDependencies": { - "@polkadot/typegen": "10.9.1", - "@types/express": "^4.17.17", - "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "@types/supertest": "^2.0.12", - "@types/time-constants": "^1.0.0", - "@typescript-eslint/parser": "^7.7.0", - "@typescript-eslint/typescript-estree": "^7.7.0", - "dotenv": "^16.3.1", - "eslint": "^8.42.0", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-config-airbnb-typescript": "^18.0.0", - "eslint-config-prettier": "^8.8.0", - "eslint-import-resolver-typescript": "^3.5.5", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-n": "^15.7.0", - "eslint-plugin-nestjs": "^1.2.3", - "eslint-plugin-prettier": "^5.0.0", - "eslint-plugin-promise": "^6.1.1", - "ioredis-mock": "^8.8.3", - "jest": "^29.5.0", - "license-report": "^6.4.0", - "nock": "^13.3.8", - "prettier": "^3.0.2", - "source-map-support": "^0.5.21", - "supertest": "^6.3.3", - "trace-unhandled": "^2.0.1", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.3", - "ts-node": "^10.9.1", + "@jest/globals": "^29.7.0", + "@nestjs/testing": "^10.3.8", + "@types/express": "^4.17.21", + "@types/jest": "^29.5.12", + "@types/node": "^20.12.10", + "@types/supertest": "^6.0.2", + "@types/time-constants": "^1.0.2", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-plugin-jest": "^28.5.0", + "ioredis-mock": "^8.9.0", + "jest": "^29.7.0", + "license-report": "^6.5.0", + "prettier": "^3.2.5", + "supertest": "^7.0.0", + "ts-jest": "^29.1.2", + "ts-loader": "^9.5.1", + "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" + "typescript": "^5.4.5", + "typescript-eslint": "^7.8.0" }, "jest": { "moduleFileExtensions": [ From 707f09a5b0229ebf883589884d971a2dd810493f Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Tue, 14 May 2024 14:52:32 -0400 Subject: [PATCH 122/137] fix: update to new messages.MessagesInBlock event (#57) - move from deprectaed MessagesStored to MessagesInBlock event - make registered webhook URLs behave like a Set so we don't add the same webhook multiple times Closes #50 #56 --- .../apps/api/src/api.service.ts | 14 ++--- .../libs/common/src/scanner/scanner.ts | 53 ++++++++++--------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index a9389da5..473a792c 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -1,5 +1,4 @@ import { Injectable, Logger } from '@nestjs/common'; -import { createHash } from 'crypto'; import { InjectRedis } from '@songkeys/nestjs-redis'; import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; @@ -64,8 +63,7 @@ export class ApiService { } public async setWebhook(webhookRegistration: WebhookRegistrationDto) { - const webhookId = createHash('sha256').update(webhookRegistration.url).digest('hex'); - this.logger.debug(`Setting webhook ${webhookId} to ${JSON.stringify(webhookRegistration)}`); + this.logger.debug(`Registering webhook ${JSON.stringify(webhookRegistration)}`); const currentRegistedWebooks = await this.redis.get(REGISTERED_WEBHOOK_KEY); let currentWebhookRegistrationDtos: { announcementType: string; urls: string[] }[] = []; @@ -73,12 +71,14 @@ export class ApiService { currentWebhookRegistrationDtos = JSON.parse(currentRegistedWebooks); } - webhookRegistration.announcementTypes.forEach((announcementType) => { - const index = currentWebhookRegistrationDtos.findIndex((webhookRegistrationDto) => webhookRegistrationDto.announcementType === announcementType.toLowerCase()); - if (index === -1) { + webhookRegistration.announcementTypes.map((a) => a.toLowerCase()).forEach((announcementType) => { + const existingRegistration = currentWebhookRegistrationDtos.find((currentWebhookRegistration) => currentWebhookRegistration.announcementType === announcementType); + if (!existingRegistration) { currentWebhookRegistrationDtos.push({ announcementType: announcementType.toLowerCase(), urls: [webhookRegistration.url] }); } else { - currentWebhookRegistrationDtos[index].urls.push(webhookRegistration.url); + const urls = new Set(existingRegistration.urls); + urls.add(webhookRegistration.url); + existingRegistration.urls = [...urls]; } }); diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index fff51ca0..71f825f1 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -6,9 +6,8 @@ import { InjectRedis } from '@songkeys/nestjs-redis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; import { Vec } from '@polkadot/types'; -import { BlockPaginationResponseMessage, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; +import { BlockPaginationResponseMessage, MessageResponse } from '@frequency-chain/api-augment/interfaces'; import { Queue } from 'bullmq'; -import { BlockNumber } from '@polkadot/types/interfaces'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; @@ -18,6 +17,7 @@ import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import * as RedisUtils from '../utils/redis'; import { MessageResponseWithSchemaId } from '../interfaces/announcement_response'; +import { ApiDecoration } from '@polkadot/api/types'; @Injectable() export class ScannerService implements OnApplicationBootstrap { @@ -38,7 +38,7 @@ export class ScannerService implements OnApplicationBootstrap { } async onApplicationBootstrap() { - const startingBlock = BigInt(this.configService.startingBlock) - 1n; + const startingBlock = Number(this.configService.startingBlock) - 1; this.setLastSeenBlockNumber(startingBlock); this.scheduleInitialScan(); this.scheduleBlockchainScan(); @@ -96,7 +96,7 @@ export class ScannerService implements OnApplicationBootstrap { this.scanInProgress = true; let lastScannedBlock = await this.getLastSeenBlockNumber(); - const currentBlockNumber = lastScannedBlock + 1n; + const currentBlockNumber = lastScannedBlock + 1; let latestBlockHash = await this.blockchainService.getBlockHash(currentBlockNumber); if (!latestBlockHash.some((byte) => byte !== 0)) { @@ -107,12 +107,14 @@ export class ScannerService implements OnApplicationBootstrap { this.logger.log(`Starting scan from block #${currentBlockNumber} (${latestBlockHash})`); while (!this.paused && !latestBlockHash.isEmpty && queueSize < this.configService.queueHighWater) { + this.logger.log(`Scanning block #${lastScannedBlock}`); // eslint-disable-next-line no-await-in-loop const at = await this.blockchainService.apiPromise.at(latestBlockHash.toHex()); // eslint-disable-next-line no-await-in-loop const events = await at.query.system.events(); + this.logger.log(`${events.length} events in block`); // eslint-disable-next-line no-await-in-loop - const messages = await this.processEvents(events, eventsToWatch); + const messages = await this.processEvents(at, lastScannedBlock, events, eventsToWatch); if (messages.length > 0) { this.logger.debug(`Found ${messages.length} messages to process`); } @@ -120,14 +122,14 @@ export class ScannerService implements OnApplicationBootstrap { await this.queueIPFSJobs(messages); // eslint-disable-next-line no-await-in-loop await this.saveProgress(lastScannedBlock); - lastScannedBlock += 1n; + lastScannedBlock += 1; // eslint-disable-next-line no-await-in-loop latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); // eslint-disable-next-line no-await-in-loop queueSize = await this.ipfsQueue.count(); } if (latestBlockHash.isEmpty) { - this.logger.log(`Scan reached end-of-chain at block ${lastScannedBlock - 1n}`); + this.logger.log(`Scan reached end-of-chain at block ${lastScannedBlock - 1}`); } else if (queueSize > this.configService.queueHighWater) { this.logger.log('Queue soft limit reached; pausing scan until next iteration'); } @@ -138,30 +140,35 @@ export class ScannerService implements OnApplicationBootstrap { } } - private async processEvents(events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { + private async processEvents(apiAt: ApiDecoration<'promise'>, blockNumber: number, events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { + const hasMessages = events.some(({ event }) => apiAt.events.messages.MessagesInBlock.is(event)); + if (!hasMessages) { + return []; + } + + const keys = await apiAt.query.messages.messagesV2.keys(blockNumber); + let schemaIds = keys.map((key) => key.args[1].toString()); + schemaIds = Array.from(new Set(...schemaIds)); const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( - events.map(async (event) => { - if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { - if (eventsToWatch?.schemaIds?.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0]?.toString())) { + schemaIds.map(async (schemaId) => { + if (eventsToWatch?.schemaIds?.length > 0 && !eventsToWatch.schemaIds.includes(schemaId)) { return null; } - const schemaId = event.event.data[0] as SchemaId; - const blockNumber = event.event.data[1] as BlockNumber; let paginationRequest = { - from_block: blockNumber.toBigInt(), + from_block: blockNumber, from_index: 0, page_size: 1000, - to_block: blockNumber.toBigInt() + 1n, + to_block: blockNumber + 1, }; let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); const messages: Vec = messageResponse.content; while (messageResponse.has_next.toHuman()) { paginationRequest = { - from_block: blockNumber.toBigInt(), + from_block: blockNumber, from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, page_size: 1000, - to_block: blockNumber.toBigInt() + 1n, + to_block: blockNumber + 1, }; // eslint-disable-next-line no-await-in-loop messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); @@ -174,8 +181,6 @@ export class ScannerService implements OnApplicationBootstrap { messages, }; return messagesWithSchemaId; - } - return null; }), ); const collectedMessages: MessageResponseWithSchemaId[] = []; @@ -215,15 +220,15 @@ export class ScannerService implements OnApplicationBootstrap { await Promise.all(promises); } - private async getLastSeenBlockNumber(): Promise { - return BigInt(((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0).toString()); + private async getLastSeenBlockNumber(): Promise { + return Number(((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0)); } - private async saveProgress(blockNumber: bigint): Promise { + private async saveProgress(blockNumber: number): Promise { await this.setLastSeenBlockNumber(blockNumber); } - private async setLastSeenBlockNumber(b: bigint): Promise { - await this.cache.setex(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, b.toString()); + private async setLastSeenBlockNumber(b: number): Promise { + await this.cache.setex(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, b); } } From e2e12335b131cae296859cbf547b3c3773d8663c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 14:55:37 -0400 Subject: [PATCH 123/137] Bump follow-redirects from 1.15.3 to 1.15.6 in /scripts/content-setup in the npm_and_yarn group across 1 directory (#42) Bumps the npm_and_yarn group with 1 update in the /scripts/content-setup directory: [follow-redirects](https://github.com/follow-redirects/follow-redirects). Updates `follow-redirects` from 1.15.3 to 1.15.6
Commits
  • 35a517c Release version 1.15.6 of the npm package.
  • c4f847f Drop Proxy-Authorization across hosts.
  • 8526b4a Use GitHub for disclosure.
  • b1677ce Release version 1.15.5 of the npm package.
  • d8914f7 Preserve fragment in responseUrl.
  • 6585820 Release version 1.15.4 of the npm package.
  • 7a6567e Disallow bracketed hostnames.
  • 05629af Prefer native URL instead of deprecated url.parse.
  • 1cba8e8 Prefer native URL instead of legacy url.resolve.
  • 72bc2a4 Simplify _processResponse error handling.
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=follow-redirects&package-manager=npm_and_yarn&previous-version=1.15.3&new-version=1.15.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/AmplicaLabs/content-watcher-service/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Joe Caputo --- .../content-watcher/scripts/content-setup/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/services/content-watcher/scripts/content-setup/package-lock.json b/services/content-watcher/scripts/content-setup/package-lock.json index ba987681..dc1817ae 100644 --- a/services/content-watcher/scripts/content-setup/package-lock.json +++ b/services/content-watcher/scripts/content-setup/package-lock.json @@ -47,9 +47,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", From 1f08dc24a96da85e80cacff56c8225125b8eb11b Mon Sep 17 00:00:00 2001 From: Wil Wade Date: Tue, 14 May 2024 14:56:53 -0400 Subject: [PATCH 124/137] Removes the use of ci-base-image for releases (#48) Unneeded container image --- services/content-watcher/.github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/services/content-watcher/.github/workflows/release.yml b/services/content-watcher/.github/workflows/release.yml index a56a734d..9e3c13f8 100644 --- a/services/content-watcher/.github/workflows/release.yml +++ b/services/content-watcher/.github/workflows/release.yml @@ -26,7 +26,6 @@ jobs: build-and-publish-container-image: name: Build and publish container image runs-on: ubuntu-latest - container: ghcr.io/libertydsnp/frequency/ci-base-image steps: - name: Validate Version Tag if: env.NEW_RELEASE_TAG_FROM_UI != '' From b913fc6512f53bbba0fd6d21b6b3dc6d2da0821e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 May 2024 15:08:04 -0400 Subject: [PATCH 125/137] Bump typescript-eslint from 7.8.0 to 7.9.0 (#54) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 7.8.0 to 7.9.0.
Release notes

Sourced from typescript-eslint's releases.

v7.9.0

7.9.0 (2024-05-13)

🚀 Features

  • rule-tester: check for missing placeholder data in the message (#9039)

🩹 Fixes

  • do not pass tsconfig canonical file name to typescript API to get program details for config file (#9042)
  • eslint-plugin: [explicit-function-return-types] fix false positive on default parameters (#9045)

❤️ Thank You

  • Kirk Waiblinger
  • Sheetal Nandi
  • Vinccool96

You can read about our versioning strategy and releases on our website.

Changelog

Sourced from typescript-eslint's changelog.

7.9.0 (2024-05-13)

This was a version bump only for typescript-eslint to align it with other projects, there were no code changes.

You can read about our versioning strategy and releases on our website.

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=typescript-eslint&package-manager=npm_and_yarn&previous-version=7.8.0&new-version=7.9.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
--------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Joe Caputo --- services/content-watcher/.tool-versions | 2 +- services/content-watcher/package-lock.json | 132 ++++++++------------- services/content-watcher/package.json | 2 +- 3 files changed, 51 insertions(+), 85 deletions(-) diff --git a/services/content-watcher/.tool-versions b/services/content-watcher/.tool-versions index 8ead549e..958fb369 100644 --- a/services/content-watcher/.tool-versions +++ b/services/content-watcher/.tool-versions @@ -1 +1 @@ -nodejs 18.16.0 +nodejs 20.12.2 diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index b2ce4e69..d47d4e74 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -68,7 +68,7 @@ "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", "typescript": "^5.4.5", - "typescript-eslint": "^7.8.0" + "typescript-eslint": "^7.9.0" } }, "node_modules/@ampproject/remapping": { @@ -4987,12 +4987,6 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, - "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 - }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -5088,21 +5082,19 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", - "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz", + "integrity": "sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.8.0", - "@typescript-eslint/type-utils": "7.8.0", - "@typescript-eslint/utils": "7.8.0", - "@typescript-eslint/visitor-keys": "7.8.0", - "debug": "^4.3.4", + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/type-utils": "7.9.0", + "@typescript-eslint/utils": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.6.0", "ts-api-utils": "^1.3.0" }, "engines": { @@ -5122,39 +5114,16 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/@typescript-eslint/parser": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", - "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.9.0.tgz", + "integrity": "sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.8.0", - "@typescript-eslint/types": "7.8.0", - "@typescript-eslint/typescript-estree": "7.8.0", - "@typescript-eslint/visitor-keys": "7.8.0", + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/typescript-estree": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", "debug": "^4.3.4" }, "engines": { @@ -5197,13 +5166,13 @@ "dev": true }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", - "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz", + "integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.8.0", - "@typescript-eslint/visitor-keys": "7.8.0" + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5214,13 +5183,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz", - "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.9.0.tgz", + "integrity": "sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.8.0", - "@typescript-eslint/utils": "7.8.0", + "@typescript-eslint/typescript-estree": "7.9.0", + "@typescript-eslint/utils": "7.9.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -5264,9 +5233,9 @@ "dev": true }, "node_modules/@typescript-eslint/types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", - "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz", + "integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5277,13 +5246,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", - "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz", + "integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.8.0", - "@typescript-eslint/visitor-keys": "7.8.0", + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/visitor-keys": "7.9.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -5352,18 +5321,15 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz", - "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.9.0.tgz", + "integrity": "sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.15", - "@types/semver": "^7.5.8", - "@typescript-eslint/scope-manager": "7.8.0", - "@typescript-eslint/types": "7.8.0", - "@typescript-eslint/typescript-estree": "7.8.0", - "semver": "^7.6.0" + "@typescript-eslint/scope-manager": "7.9.0", + "@typescript-eslint/types": "7.9.0", + "@typescript-eslint/typescript-estree": "7.9.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -5377,12 +5343,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", - "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz", + "integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/types": "7.9.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -12817,14 +12783,14 @@ } }, "node_modules/typescript-eslint": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.8.0.tgz", - "integrity": "sha512-sheFG+/D8N/L7gC3WT0Q8sB97Nm573Yfr+vZFzl/4nBdYcmviBPtwGSX9TJ7wpVg28ocerKVOt+k2eGmHzcgVA==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.9.0.tgz", + "integrity": "sha512-7iTn9c10teHHCys5Ud/yaJntXZrjt3h2mrx3feJGBOLgQkF3TB1X89Xs3aVQ/GgdXRAXpk2bPTdpRwHP4YkUow==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "7.8.0", - "@typescript-eslint/parser": "7.8.0", - "@typescript-eslint/utils": "7.8.0" + "@typescript-eslint/eslint-plugin": "7.9.0", + "@typescript-eslint/parser": "7.9.0", + "@typescript-eslint/utils": "7.9.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index a17fc822..e274035d 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -97,7 +97,7 @@ "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", "typescript": "^5.4.5", - "typescript-eslint": "^7.8.0" + "typescript-eslint": "^7.9.0" }, "jest": { "moduleFileExtensions": [ From d74bdf5ddad10d3a20ea686d0ecaeb9b020d9b47 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Thu, 16 May 2024 14:08:29 -0400 Subject: [PATCH 126/137] feat: generate types for webhook from OpenAPI spec (#58) - Add OpenAPI spec document for webhook - Add tooling to generate webhook types - Remove manually defined types for webhook - Change all blockNumber variables from bigint -> number as it's only a u32 - Lint/formatting --- .../apps/api/src/api.service.ts | 22 +- .../content-announcement.openapi.json | 276 +++++++++ .../libs/common/src/config/swagger_config.ts | 5 + .../libs/common/src/crawler/crawler.ts | 7 +- .../libs/common/src/interfaces/dsnp.ts | 222 -------- .../src/interfaces/ipfs.job.interface.ts | 6 +- ....ts => message_response_with_schema_id.ts} | 8 - .../libs/common/src/ipfs/ipfs.dsnp.ts | 4 +- .../common/src/pubsub/announcers/broadcast.ts | 2 +- .../common/src/pubsub/announcers/profile.ts | 2 +- .../common/src/pubsub/announcers/reaction.ts | 2 +- .../common/src/pubsub/announcers/reply.ts | 2 +- .../common/src/pubsub/announcers/tombstone.ts | 2 +- .../common/src/pubsub/announcers/update.ts | 2 +- .../libs/common/src/pubsub/pubsub.service.ts | 2 +- .../libs/common/src/scanner/scanner.module.ts | 1 + .../libs/common/src/scanner/scanner.ts | 114 ++-- .../src/types/content-announcement/index.ts | 2 + .../types/content-announcement/types.gen.ts | 66 +++ services/content-watcher/openapi-ts.config.ts | 16 + services/content-watcher/package-lock.json | 536 ++++++++++++++++++ services/content-watcher/package.json | 4 +- 22 files changed, 991 insertions(+), 312 deletions(-) create mode 100644 services/content-watcher/content-announcement.openapi.json delete mode 100644 services/content-watcher/libs/common/src/interfaces/dsnp.ts rename services/content-watcher/libs/common/src/interfaces/{announcement_response.ts => message_response_with_schema_id.ts} (55%) create mode 100644 services/content-watcher/libs/common/src/types/content-announcement/index.ts create mode 100644 services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts create mode 100644 services/content-watcher/openapi-ts.config.ts diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 473a792c..eee2396b 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -71,16 +71,18 @@ export class ApiService { currentWebhookRegistrationDtos = JSON.parse(currentRegistedWebooks); } - webhookRegistration.announcementTypes.map((a) => a.toLowerCase()).forEach((announcementType) => { - const existingRegistration = currentWebhookRegistrationDtos.find((currentWebhookRegistration) => currentWebhookRegistration.announcementType === announcementType); - if (!existingRegistration) { - currentWebhookRegistrationDtos.push({ announcementType: announcementType.toLowerCase(), urls: [webhookRegistration.url] }); - } else { - const urls = new Set(existingRegistration.urls); - urls.add(webhookRegistration.url); - existingRegistration.urls = [...urls]; - } - }); + webhookRegistration.announcementTypes + .map((a) => a.toLowerCase()) + .forEach((announcementType) => { + const existingRegistration = currentWebhookRegistrationDtos.find((currentWebhookRegistration) => currentWebhookRegistration.announcementType === announcementType); + if (!existingRegistration) { + currentWebhookRegistrationDtos.push({ announcementType: announcementType.toLowerCase(), urls: [webhookRegistration.url] }); + } else { + const urls = new Set(existingRegistration.urls); + urls.add(webhookRegistration.url); + existingRegistration.urls = [...urls]; + } + }); await this.redis.set(REGISTERED_WEBHOOK_KEY, JSON.stringify(currentWebhookRegistrationDtos)); } diff --git a/services/content-watcher/content-announcement.openapi.json b/services/content-watcher/content-announcement.openapi.json new file mode 100644 index 00000000..90f1381f --- /dev/null +++ b/services/content-watcher/content-announcement.openapi.json @@ -0,0 +1,276 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Content Announcement API", + "version": "1.0.0" + }, + "paths": { + "/content-announcements": { + "post": { + "summary": "Create a new content announcement", + "operationId": "createAnnouncementResponse", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AnnouncementResponse" + } + } + } + }, + "responses": { + "201": { + "description": "Content announcement created successfully" + }, + "400": { + "description": "Bad request" + } + } + } + } + }, + "components": { + "schemas": { + "AnnouncementType": { + "enum": [ + 0, + 2, + 3, + 4, + 5, + 6, + 113 + ], + "x-enum-varnames": [ + "Tombstone", + "Broadcast", + "Reply", + "Reaction", + "Profile", + "Update", + "PublicFollows" + ] + }, + "AnnouncementResponse": { + "type": "object", + "properties": { + "requestId": { + "type": "string", + "nullable": true, + "description": "An optional identifier for the request, may be used for tracking or correlation" + }, + "schemaId": { + "type": "string", + "description": "Identifier for the schema being used or referenced" + }, + "blockNumber": { + "type": "integer", + "description": "The block number on the blockchain where this announcement was recorded" + }, + "announcement": { + "oneOf": [ + { + "$ref": "#/components/schemas/TombstoneAnnouncement" + }, + { + "$ref": "#/components/schemas/BroadcastAnnouncement" + }, + { + "$ref": "#/components/schemas/ReplyAnnouncement" + }, + { + "$ref": "#/components/schemas/ReactionAnnouncement" + }, + { + "$ref": "#/components/schemas/ProfileAnnouncement" + }, + { + "$ref": "#/components/schemas/UpdateAnnouncement" + } + ] + } + }, + "required": [ + "schemaId", + "blockNumber", + "announcement" + ] + }, + "TypedAnnouncement": { + "type": "object", + "properties": { + "announcementType": { + "type": "AnnouncementType" + }, + "fromId": { + "type": "string" + } + }, + "required": [ + "announcementType", + "fromId" + ], + "discriminator": { + "propertyName": "announcementType", + "mapping": { + "0": "#/components/schemas/TombstoneAnnouncement", + "2": "#/components/schemas/BroadcastAnnouncement", + "3": "#/components/schemas/ReplyAnnouncement", + "4": "#/components/schemas/ReactionAnnouncement", + "5": "#/components/schemas/ProfileAnnouncement", + "6": "#/components/schemas/UpdateAnnouncement" + } + } + }, + "TombstoneAnnouncement": { + "allOf": [ + { + "$ref": "#/components/schemas/TypedAnnouncement" + }, + { + "type": "object", + "properties": { + "targetAnnouncementType": { + "type": "integer" + }, + "targetContentHash": { + "type": "string" + } + }, + "required": [ + "targetAnnouncementType", + "targetContentHash" + ] + } + ] + }, + "BroadcastAnnouncement": { + "allOf": [ + { + "$ref": "#/components/schemas/TypedAnnouncement" + }, + { + "type": "object", + "properties": { + "contentHash": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "contentHash", + "url" + ] + } + ] + }, + "ReplyAnnouncement": { + "allOf": [ + { + "$ref": "#/components/schemas/TypedAnnouncement" + }, + { + "type": "object", + "properties": { + "contentHash": { + "type": "string" + }, + "inReplyTo": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "contentHash", + "inReplyTo", + "url" + ] + } + ] + }, + "ReactionAnnouncement": { + "allOf": [ + { + "$ref": "#/components/schemas/TypedAnnouncement" + }, + { + "type": "object", + "properties": { + "emoji": { + "type": "string" + }, + "inReplyTo": { + "type": "string" + }, + "apply": { + "type": "integer" + } + }, + "required": [ + "emoji", + "inReplyTo", + "apply" + ] + } + ] + }, + "ProfileAnnouncement": { + "allOf": [ + { + "$ref": "#/components/schemas/TypedAnnouncement" + }, + { + "type": "object", + "properties": { + "contentHash": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "contentHash", + "url" + ] + } + ] + }, + "UpdateAnnouncement": { + "allOf": [ + { + "$ref": "#/components/schemas/TypedAnnouncement" + }, + { + "type": "object", + "properties": { + "contentHash": { + "type": "string" + }, + "targetAnnouncementType": { + "type": "integer" + }, + "targetContentHash": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "contentHash", + "targetAnnouncementType", + "targetContentHash", + "url" + ] + } + ] + } + } + } +} diff --git a/services/content-watcher/libs/common/src/config/swagger_config.ts b/services/content-watcher/libs/common/src/config/swagger_config.ts index ca5648e5..09fc1eec 100644 --- a/services/content-watcher/libs/common/src/config/swagger_config.ts +++ b/services/content-watcher/libs/common/src/config/swagger_config.ts @@ -1,5 +1,6 @@ import { INestApplication } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import * as fs from 'fs'; import metadata from '../../../../apps/api/src/metadata'; export const initSwagger = async (app: INestApplication, apiPath: string) => { @@ -17,5 +18,9 @@ export const initSwagger = async (app: INestApplication, apiPath: string) => { const document = SwaggerModule.createDocument(app, options, { extraModels: [], }); + fs.writeFileSync( + './swagger.json', + JSON.stringify(document, (_, v) => v, 2), + ); SwaggerModule.setup(apiPath, app, document); }; diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index 55405185..dcadb431 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -3,10 +3,9 @@ import { Injectable } from '@nestjs/common'; import { InjectQueue, Processor } from '@nestjs/bullmq'; import Redis from 'ioredis'; import { InjectRedis } from '@songkeys/nestjs-redis'; -import { Vec, u16, u32 } from '@polkadot/types'; +import { Vec } from '@polkadot/types'; import { BlockPaginationResponseMessage, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Job, Queue } from 'bullmq'; -import { firstValueFrom } from 'rxjs'; import { BlockNumber } from '@polkadot/types/interfaces'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { BlockchainService } from '../blockchain/blockchain.service'; @@ -16,7 +15,7 @@ import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { ContentSearchRequestDto } from '../dtos/request-job.dto'; import { REGISTERED_WEBHOOK_KEY } from '../constants'; -import { MessageResponseWithSchemaId } from '../interfaces/announcement_response'; +import { MessageResponseWithSchemaId } from '../interfaces/message_response_with_schema_id'; @Injectable() @Processor(QueueConstants.REQUEST_QUEUE_NAME, { @@ -138,7 +137,7 @@ export class CrawlerService extends BaseConsumer { } const ipfsQueueJob = createIPFSQueueJob( - message.block_number.toString(), + message.block_number.toNumber(), message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), message.provider_msa_id.toString(), schemaId, diff --git a/services/content-watcher/libs/common/src/interfaces/dsnp.ts b/services/content-watcher/libs/common/src/interfaces/dsnp.ts deleted file mode 100644 index c7ab5e3c..00000000 --- a/services/content-watcher/libs/common/src/interfaces/dsnp.ts +++ /dev/null @@ -1,222 +0,0 @@ -/** - * AnnouncementType: an enum representing different types of DSNP announcements - */ - -import { ActivityContentNote } from '@dsnp/activity-content/types'; - -// eslint-disable-next-line no-shadow -export enum AnnouncementType { - Tombstone = 0, - Broadcast = 2, - Reply = 3, - Reaction = 4, - Profile = 5, - Update = 6, - PublicFollows = 113, -} - -type TombstoneFields = { - announcementType: AnnouncementType.Tombstone; - targetAnnouncementType: AnnouncementType; - targetContentHash: string; -}; - -type BroadcastFields = { - announcementType: AnnouncementType.Broadcast; - contentHash: string; - url: string; -}; - -type ReplyFields = { - announcementType: AnnouncementType.Reply; - contentHash: string; - inReplyTo: string; - url: string; -}; - -type ReactionFields = { - announcementType: AnnouncementType.Reaction; - emoji: string; - inReplyTo: string; - apply: number; -}; - -type ProfileFields = { - announcementType: AnnouncementType.Profile; - contentHash: string; - url: string; -}; - -type UpdateFields = { - announcementType: AnnouncementType.Update; - contentHash: string; - targetAnnouncementType: AnnouncementType; - targetContentHash: string; - url: string; -}; - -/** - * TypedAnnouncement: an Announcement with a particular AnnouncementType - */ -export type TypedAnnouncement = { - announcementType: T; - fromId: string; -} & (TombstoneFields | BroadcastFields | ReplyFields | ReactionFields | ProfileFields | UpdateFields); - -/** - * Announcement: an Announcement intended for inclusion in a batch file - */ -export type Announcement = TypedAnnouncement; - -/** - * ProfileAnnouncement: an Announcement of type Profile - */ -export type ProfileAnnouncement = TypedAnnouncement; - -/** - * TombstoneAnnouncement: an Announcement of type Tombstone - */ -export type TombstoneAnnouncement = TypedAnnouncement; - -/** - * BroadcastAnnouncement: an Announcement of type Broadcast - */ -export type BroadcastAnnouncement = TypedAnnouncement; - -/** - * ReplyAnnouncement: am announcement of type Reply - */ -export type ReplyAnnouncement = TypedAnnouncement; - -/** - * ReactionAnnouncement: an Announcement of type Reaction - */ -export type ReactionAnnouncement = TypedAnnouncement; - -/** - * UpdateAnnouncement: an Announcement of type Update - */ -export type UpdateAnnouncement = TypedAnnouncement; - -/** - * createTombstone() generates a tombstone announcement from a given URL and - * hash. - * - * @param fromId - The id of the user from whom the announcement is posted - * @param targetType - The DSNP announcement type of the target announcement - * @param targetSignature - The signature of the target announcement - * @returns A TombstoneAnnouncement - */ -export const createTombstone = (fromId: string, targetType: AnnouncementType, targetContentHash: string): TombstoneAnnouncement => ({ - announcementType: AnnouncementType.Tombstone, - targetAnnouncementType: targetType, - targetContentHash, - fromId, -}); - -/** - * createBroadcast() generates a broadcast announcement from a given URL and - * hash. - * - * @param fromId - The id of the user from whom the announcement is posted - * @param url - The URL of the activity content to reference - * @param hash - The hash of the content at the URL - * @returns A BroadcastAnnouncement - */ -export const createBroadcast = (fromId: string, url: string, hash: string): BroadcastAnnouncement => ({ - announcementType: AnnouncementType.Broadcast, - contentHash: hash, - fromId, - url, -}); - -/** - * createReply() generates a reply announcement from a given URL, hash and - * content uri. - * - * @param fromId - The id of the user from whom the announcement is posted - * @param url - The URL of the activity content to reference - * @param hash - The hash of the content at the URL - * @param inReplyTo - The DSNP Content Uri of the parent announcement - * @returns A ReplyAnnouncement - */ -export const createReply = (fromId: string, url: string, hash: string, inReplyTo: string): ReplyAnnouncement => ({ - announcementType: AnnouncementType.Reply, - contentHash: hash, - fromId, - inReplyTo, - url, -}); - -/** - * createReaction() generates a reaction announcement from a given URL, hash and - * content uri. - * - * @param fromId - The id of the user from whom the announcement is posted - * @param emoji - The emoji to respond with - * @param inReplyTo - The DSNP Content Uri of the parent announcement - * @param apply - - * @returns A ReactionAnnouncement - */ -export const createReaction = (fromId: string, emoji: string, inReplyTo: string, apply: number): ReactionAnnouncement => ({ - announcementType: AnnouncementType.Reaction, - emoji, - apply, - fromId, - inReplyTo, -}); - -/** - * createProfile() generates a profile announcement from a given URL and hash. - * - * @param fromId - The id of the user from whom the announcement is posted - * @param url - The URL of the activity content to reference - * @param hash - The hash of the content at the URL - * @returns A ProfileAnnouncement - */ -export const createProfile = (fromId: string, url: string, hash: string): ProfileAnnouncement => ({ - announcementType: AnnouncementType.Profile, - contentHash: hash, - fromId, - url, -}); - -/** - * createNote() provides a simple factory for generating an ActivityContentNote - * object. - * @param content - The text content to include in the note - * @param published - the Date that the note was claimed to be published - * @param options - Overrides default fields for the ActivityContentNote - * @returns An ActivityContentNote object - */ -export const createNote = (content: string, published: Date, options?: Partial): ActivityContentNote => ({ - '@context': 'https://www.w3.org/ns/activitystreams', - type: 'Note', - mediaType: 'text/plain', - published: published.toISOString(), - content, - ...options, -}); - -/** - * createUpdate() generates an update announcement from a given URL, hash and - * content uri. - * @param fromId - The id of the user from whom the announcement is posted - * @param url - The URL of the activity content to reference - * @param hash - The hash of the content at the URL - * @param targetType - The DSNP announcement type of the target announcement - * @param targetHash - The hash of the target announcement - * @returns An UpdateAnnouncement - * @remarks - * The targetHash is the hash of the target announcement. This is used to - * ensure that the target announcement has not been modified since the update - * announcement was created. - */ -export const createUpdate = (fromId: string, url: string, hash: string, targetType: AnnouncementType, targetHash: string): UpdateAnnouncement => ({ - announcementType: AnnouncementType.Update, - fromId, - contentHash: hash, - targetAnnouncementType: targetType, - targetContentHash: targetHash, - url, -}); diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index c11bac11..6079203a 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -3,13 +3,13 @@ export interface IIPFSJob { providerId: string; schemaId: string; cid: string; - blockNumber: string; + blockNumber: number; index: number; requestId?: string; } export function createIPFSQueueJob( - blockNumber: string, + blockNumber: number, msaId: string, providerId: string, schemaId: string, @@ -27,6 +27,6 @@ export function createIPFSQueueJob( blockNumber, index, requestId, - } as IIPFSJob, + }, }; } diff --git a/services/content-watcher/libs/common/src/interfaces/announcement_response.ts b/services/content-watcher/libs/common/src/interfaces/message_response_with_schema_id.ts similarity index 55% rename from services/content-watcher/libs/common/src/interfaces/announcement_response.ts rename to services/content-watcher/libs/common/src/interfaces/message_response_with_schema_id.ts index 7928700e..6059e7df 100644 --- a/services/content-watcher/libs/common/src/interfaces/announcement_response.ts +++ b/services/content-watcher/libs/common/src/interfaces/message_response_with_schema_id.ts @@ -1,13 +1,5 @@ import { MessageResponse } from '@frequency-chain/api-augment/interfaces'; import { Vec } from '@polkadot/types'; -import { Announcement } from './dsnp'; - -export interface AnnouncementResponse { - requestId?: string; - schemaId: string; - blockNumber: string; - announcement: Announcement; -} export interface MessageResponseWithSchemaId { schemaId: string; diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index 7b7c1b3b..9fe4c159 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -11,6 +11,7 @@ import { IIPFSJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; import { + AnnouncementResponse, AnnouncementType, BroadcastAnnouncement, ProfileAnnouncement, @@ -18,8 +19,7 @@ import { ReplyAnnouncement, TombstoneAnnouncement, UpdateAnnouncement, -} from '../interfaces/dsnp'; -import { AnnouncementResponse } from '../interfaces/announcement_response'; +} from '../types/content-announcement'; @Injectable() @Processor(QueueConstants.IPFS_QUEUE, { diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts b/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts index 57027bc8..7b3adf8a 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; -import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; +import { AnnouncementResponse } from '../../types/content-announcement'; @Injectable() @Processor(QueueConstants.BROADCAST_QUEUE_NAME, { concurrency: 2 }) diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts b/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts index ebcb85de..a13fcd9e 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; -import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; +import { AnnouncementResponse } from '../../types/content-announcement'; @Injectable() @Processor(QueueConstants.PROFILE_QUEUE_NAME, { concurrency: 2 }) diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts b/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts index 5af38f2b..121935a9 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; -import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; +import { AnnouncementResponse } from '../../types/content-announcement'; @Injectable() @Processor(QueueConstants.REACTION_QUEUE_NAME, { concurrency: 2 }) diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts b/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts index a68bad97..e95333f1 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; -import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; +import { AnnouncementResponse } from '../../types/content-announcement'; @Injectable() @Processor(QueueConstants.REPLY_QUEUE_NAME, { concurrency: 2 }) diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts b/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts index a179aad0..7d5cba51 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; -import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; +import { AnnouncementResponse } from '../../types/content-announcement'; @Injectable() @Processor(QueueConstants.TOMBSTONE_QUEUE_NAME, { concurrency: 2 }) diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/update.ts b/services/content-watcher/libs/common/src/pubsub/announcers/update.ts index 6e22033b..06cdace2 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/update.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/update.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; import * as QueueConstants from '../../utils/queues'; import { BaseConsumer } from '../../utils/base-consumer'; -import { AnnouncementResponse } from '../../interfaces/announcement_response'; import { PubSubService } from '../pubsub.service'; +import { AnnouncementResponse } from '../../types/content-announcement'; @Injectable() @Processor(QueueConstants.UPDATE_QUEUE_NAME, { concurrency: 2 }) diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts index b04ce983..fc5f8949 100644 --- a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts @@ -4,9 +4,9 @@ import Redis from 'ioredis'; import axios from 'axios'; import { MILLISECONDS_PER_SECOND } from 'time-constants'; import { EVENTS_TO_WATCH_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; -import { AnnouncementResponse } from '../interfaces/announcement_response'; import { ConfigService } from '../config/config.service'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; +import { AnnouncementResponse } from '../types/content-announcement'; @Injectable() export class PubSubService { diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts index 43b1c119..4f59c877 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.module.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -2,6 +2,7 @@ https://docs.nestjs.com/modules */ +import '@frequency-chain/api-augment'; import { Module } from '@nestjs/common'; import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 71f825f1..781df07e 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -1,4 +1,6 @@ /* eslint-disable no-underscore-dangle */ +import '@frequency-chain/api-augment'; +import { BlockPaginationResponseMessage, MessageResponse } from '@frequency-chain/api-augment/interfaces'; import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import Redis from 'ioredis'; @@ -6,7 +8,6 @@ import { InjectRedis } from '@songkeys/nestjs-redis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; import { Vec } from '@polkadot/types'; -import { BlockPaginationResponseMessage, MessageResponse } from '@frequency-chain/api-augment/interfaces'; import { Queue } from 'bullmq'; import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { ConfigService } from '../config/config.service'; @@ -16,7 +17,7 @@ import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEB import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import * as RedisUtils from '../utils/redis'; -import { MessageResponseWithSchemaId } from '../interfaces/announcement_response'; +import { MessageResponseWithSchemaId } from '../interfaces/message_response_with_schema_id'; import { ApiDecoration } from '@polkadot/api/types'; @Injectable() @@ -107,12 +108,10 @@ export class ScannerService implements OnApplicationBootstrap { this.logger.log(`Starting scan from block #${currentBlockNumber} (${latestBlockHash})`); while (!this.paused && !latestBlockHash.isEmpty && queueSize < this.configService.queueHighWater) { - this.logger.log(`Scanning block #${lastScannedBlock}`); // eslint-disable-next-line no-await-in-loop const at = await this.blockchainService.apiPromise.at(latestBlockHash.toHex()); // eslint-disable-next-line no-await-in-loop const events = await at.query.system.events(); - this.logger.log(`${events.length} events in block`); // eslint-disable-next-line no-await-in-loop const messages = await this.processEvents(at, lastScannedBlock, events, eventsToWatch); if (messages.length > 0) { @@ -140,7 +139,12 @@ export class ScannerService implements OnApplicationBootstrap { } } - private async processEvents(apiAt: ApiDecoration<'promise'>, blockNumber: number, events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { + private async processEvents( + apiAt: ApiDecoration<'promise'>, + blockNumber: number, + events: Vec, + eventsToWatch: ChainWatchOptionsDto, + ): Promise { const hasMessages = events.some(({ event }) => apiAt.events.messages.MessagesInBlock.is(event)); if (!hasMessages) { return []; @@ -151,36 +155,36 @@ export class ScannerService implements OnApplicationBootstrap { schemaIds = Array.from(new Set(...schemaIds)); const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( schemaIds.map(async (schemaId) => { - if (eventsToWatch?.schemaIds?.length > 0 && !eventsToWatch.schemaIds.includes(schemaId)) { - return null; - } - let paginationRequest = { + if (eventsToWatch?.schemaIds?.length > 0 && !eventsToWatch.schemaIds.includes(schemaId)) { + return null; + } + let paginationRequest = { + from_block: blockNumber, + from_index: 0, + page_size: 1000, + to_block: blockNumber + 1, + }; + + let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + const messages: Vec = messageResponse.content; + while (messageResponse.has_next.toHuman()) { + paginationRequest = { from_block: blockNumber, - from_index: 0, + from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, page_size: 1000, to_block: blockNumber + 1, }; - - let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - const messages: Vec = messageResponse.content; - while (messageResponse.has_next.toHuman()) { - paginationRequest = { - from_block: blockNumber, - from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, - page_size: 1000, - to_block: blockNumber + 1, - }; - // eslint-disable-next-line no-await-in-loop - messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - if (messageResponse.content.length > 0) { - messages.push(...messageResponse.content); - } + // eslint-disable-next-line no-await-in-loop + messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + if (messageResponse.content.length > 0) { + messages.push(...messageResponse.content); } - const messagesWithSchemaId: MessageResponseWithSchemaId = { - schemaId: schemaId.toString(), - messages, - }; - return messagesWithSchemaId; + } + const messagesWithSchemaId: MessageResponseWithSchemaId = { + schemaId: schemaId.toString(), + messages, + }; + return messagesWithSchemaId; }), ); const collectedMessages: MessageResponseWithSchemaId[] = []; @@ -193,35 +197,35 @@ export class ScannerService implements OnApplicationBootstrap { } private async queueIPFSJobs(messages: MessageResponseWithSchemaId[]): Promise { - const promises = messages.map(async (messageResponse) => { - const { schemaId } = messageResponse; - const innerPromises = messageResponse.messages.map(async (message) => { - if (!message.cid || message.cid.isNone) { - return; - } - - const ipfsQueueJob = createIPFSQueueJob( - message.block_number.toString(), - message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), - message.provider_msa_id.toString(), - schemaId, - message.cid.unwrap().toString(), - message.index.toNumber(), - '', - ); - // eslint-disable-next-line no-await-in-loop - await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); - }); - - // eslint-disable-next-line no-await-in-loop - await Promise.all(innerPromises); - }); + const jobs = messages.flatMap((messageResponse) => + messageResponse.messages + .filter((message) => message.cid && message.cid.isSome) + .map((message) => { + const job = createIPFSQueueJob( + message.block_number.toNumber(), + message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), + message.provider_msa_id.toString(), + messageResponse.schemaId, + message.cid.unwrap().toString(), + message.index.toNumber(), + '', + ); + + return { + name: `IPFS Job: ${job.key}`, + data: job.data, + opts: { jobId: job.key }, + }; + }), + ); - await Promise.all(promises); + if (jobs && jobs.length > 0) { + await this.ipfsQueue.addBulk(jobs); + } } private async getLastSeenBlockNumber(): Promise { - return Number(((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0)); + return Number((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0); } private async saveProgress(blockNumber: number): Promise { diff --git a/services/content-watcher/libs/common/src/types/content-announcement/index.ts b/services/content-watcher/libs/common/src/types/content-announcement/index.ts new file mode 100644 index 00000000..343d3c8f --- /dev/null +++ b/services/content-watcher/libs/common/src/types/content-announcement/index.ts @@ -0,0 +1,2 @@ +// This file is auto-generated by @hey-api/openapi-ts +export * from './types.gen'; diff --git a/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts b/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts new file mode 100644 index 00000000..cae2fdb1 --- /dev/null +++ b/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts @@ -0,0 +1,66 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export enum AnnouncementType { + Tombstone = 0, + Broadcast = 2, + Reply = 3, + Reaction = 4, + Profile = 5, + Update = 6, + PublicFollows = 113, +} + +export type AnnouncementResponse = { + /** + * An optional identifier for the request, may be used for tracking or correlation + */ + requestId?: string | null; + /** + * Identifier for the schema being used or referenced + */ + schemaId: string; + /** + * The block number on the blockchain where this announcement was recorded + */ + blockNumber: number; + announcement: TombstoneAnnouncement | BroadcastAnnouncement | ReplyAnnouncement | ReactionAnnouncement | ProfileAnnouncement | UpdateAnnouncement; +}; + +export type TypedAnnouncement = { + announcementType: AnnouncementType; + fromId: string; +}; + +export type TombstoneAnnouncement = TypedAnnouncement & { + targetAnnouncementType: number; + targetContentHash: string; +}; + +export type BroadcastAnnouncement = TypedAnnouncement & { + contentHash: string; + url: string; +}; + +export type ReplyAnnouncement = TypedAnnouncement & { + contentHash: string; + inReplyTo: string; + url: string; +}; + +export type ReactionAnnouncement = TypedAnnouncement & { + emoji: string; + inReplyTo: string; + apply: number; +}; + +export type ProfileAnnouncement = TypedAnnouncement & { + contentHash: string; + url: string; +}; + +export type UpdateAnnouncement = TypedAnnouncement & { + contentHash: string; + targetAnnouncementType: number; + targetContentHash: string; + url: string; +}; diff --git a/services/content-watcher/openapi-ts.config.ts b/services/content-watcher/openapi-ts.config.ts new file mode 100644 index 00000000..01710ff2 --- /dev/null +++ b/services/content-watcher/openapi-ts.config.ts @@ -0,0 +1,16 @@ +import { defineConfig, UserConfig } from '@hey-api/openapi-ts'; + +export default defineConfig({ + client: 'axios', + exportCore: false, + output: { + format: 'prettier', + lint: 'eslint', + path: 'types' + }, + schemas: false, + services: false, + types: { + enums: 'typescript' + } +} as UserConfig); diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index d47d4e74..e483f953 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -47,6 +47,7 @@ "time-constants": "^1.0.3" }, "devDependencies": { + "@hey-api/openapi-ts": "^0.45.1", "@jest/globals": "^29.7.0", "@nestjs/testing": "^10.3.8", "@types/express": "^4.17.21", @@ -245,6 +246,23 @@ "node": ">=0.12.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.1.tgz", + "integrity": "sha512-DxjgKBCoyReu4p5HMvpmgSOfRhhBcuf5V5soDDRgOTZMwsA4KSFzol1abFZgiCTE11L2kKGca5Md9GwDdXVBwQ==", + "dev": true, + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, "node_modules/@assemblyscript/loader": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.9.4.tgz", @@ -2128,6 +2146,49 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.45.1", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.45.1.tgz", + "integrity": "sha512-TT4YC9SshgruHnr/z47LD945hFhefuD6xSfdt9+fv/sU+shP0nPJhNdyt71oMGTAB9h6nsrjC8z84ZnoAGKHrg==", + "dev": true, + "dependencies": { + "@apidevtools/json-schema-ref-parser": "11.6.1", + "c12": "1.10.0", + "camelcase": "8.0.0", + "commander": "12.0.0", + "handlebars": "4.7.8" + }, + "bin": { + "openapi-ts": "bin/index.cjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/@hey-api/openapi-ts/node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@hey-api/openapi-ts/node_modules/commander": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", + "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -2748,6 +2809,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, "node_modules/@kessler/tableify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@kessler/tableify/-/tableify-1.0.2.tgz", @@ -6120,6 +6187,26 @@ "node": ">= 0.8" } }, + "node_modules/c12": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-1.10.0.tgz", + "integrity": "sha512-0SsG7UDhoRWcuSvKWHaXmu5uNjDCDN3nkQLRL4Q42IlFy+ze58FcCoI3uPwINXinkz7ZinbhEgyzYFw9u9ZV8g==", + "dev": true, + "dependencies": { + "chokidar": "^3.6.0", + "confbox": "^0.1.3", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.1", + "jiti": "^1.21.0", + "mlly": "^1.6.1", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.0.3", + "rc9": "^2.1.1" + } + }, "node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -6276,6 +6363,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -6323,6 +6419,24 @@ "multiformats": "^9.4.2" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/citty/node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/cjs-module-lexer": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", @@ -6556,6 +6670,12 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true + }, "node_modules/consola": { "version": "2.15.3", "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", @@ -6861,6 +6981,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6885,6 +7011,12 @@ "node": ">= 0.8" } }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -7904,6 +8036,36 @@ "node": ">=12" } }, + "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, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "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, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/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 + }, "node_modules/fs-monkey": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", @@ -8001,6 +8163,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/giget": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", + "dev": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "tar": "^6.2.0" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/giget/node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/glob": { "version": "10.3.10", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", @@ -8163,6 +8353,36 @@ "multiformats": "^9.4.2" } }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -9542,6 +9762,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/joi": { "version": "17.13.1", "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", @@ -10141,6 +10370,37 @@ "node": ">=16 || 14 >=14.17" } }, + "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, + "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, + "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 + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -10152,6 +10412,18 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mlly": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.0.tgz", + "integrity": "sha512-U9SDaXGEREBYQgfejV97coK0UL1r+qnF2SyO9A3qcI8MzKnsIFKHNVEkrDyNncQTKQQumsasmeq84eNMdBfsNQ==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.0", + "ufo": "^1.5.3" + } + }, "node_modules/mock-socket": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/mock-socket/-/mock-socket-9.3.1.tgz", @@ -10425,6 +10697,12 @@ } } }, + "node_modules/node-fetch-native": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", + "dev": true + }, "node_modules/node-gyp-build-optional-packages": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", @@ -10492,6 +10770,156 @@ "node": ">=8" } }, + "node_modules/nypm": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.8.tgz", + "integrity": "sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==", + "dev": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "ufo": "^1.4.0" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/nypm/node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/nypm/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/nypm/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/nypm/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10508,6 +10936,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz", + "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", + "dev": true + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -10751,6 +11185,18 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -10840,6 +11286,17 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz", + "integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==", + "dev": true, + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.0", + "pathe": "^1.1.2" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -11136,6 +11593,16 @@ "node": ">=0.10.0" } }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -12235,6 +12702,50 @@ "node": ">=6" } }, + "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, + "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/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "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, + "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 + }, "node_modules/terser": { "version": "5.31.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", @@ -12808,6 +13319,25 @@ } } }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", @@ -13133,6 +13663,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index e274035d..f64dab00 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -24,7 +24,8 @@ "test": "jest --coverage --verbose", "test:e2e": "set -a ; . ./.env.dev; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", "local:init": "node scripts/chain-setup/local-chain-setup.cjs", - "local:publish": "cd scripts/content-setup && npm i && npm run main" + "local:publish": "cd scripts/content-setup && npm i && npm run main", + "generate-content-announcement-types": "npx @hey-api/openapi-ts -i content-announcement.openapi.json -o libs/common/src/types/content-announcement" }, "repository": { "type": "git", @@ -76,6 +77,7 @@ "time-constants": "^1.0.3" }, "devDependencies": { + "@hey-api/openapi-ts": "^0.45.1", "@jest/globals": "^29.7.0", "@nestjs/testing": "^10.3.8", "@types/express": "^4.17.21", From b42c5dd449c69b60cedd49234bb8d9509250ade2 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Fri, 17 May 2024 16:17:58 -0400 Subject: [PATCH 127/137] fix: crawler update to new MessagesInBlock event (#59) # Description `crawler` service was stlil using the deprected `MessagesStored` event. Updated/refactored to use common logic with `scanner` to look for `MessagesInBlock` events. Also changed all occurrences of Schema IDs and Block Numbers to JS `number` types, as SchemaId is `u16` and Block Number is `u32` (no need to handle `u64`/`bigint`, so no need to transmit as strings) --- .../content-watcher/apps/api/src/metadata.ts | 99 +------------- .../content-announcement.openapi.json | 2 +- .../src/blockchain/blockchain.module.ts | 5 +- .../chain-event-processor.service.ts | 111 +++++++++++++++ .../libs/common/src/crawler/crawler.ts | 127 ++---------------- .../libs/common/src/dtos/chain.watch.dto.ts | 12 +- .../libs/common/src/dtos/common.dto.ts | 4 +- .../libs/common/src/dtos/request-job.dto.ts | 16 ++- .../src/interfaces/ipfs.job.interface.ts | 6 +- .../message_response_with_schema_id.ts | 2 +- .../libs/common/src/scanner/scanner.ts | 121 ++--------------- .../src/types/content-announcement/index.ts | 2 +- .../types/content-announcement/types.gen.ts | 78 +++++------ 13 files changed, 208 insertions(+), 377 deletions(-) create mode 100644 services/content-watcher/libs/common/src/blockchain/chain-event-processor.service.ts diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index 988f46b3..7e2aac3e 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -1,94 +1,9 @@ /* eslint-disable */ export default async () => { - const t = { - ['../../../libs/common/src/dtos/activity.dto']: await import('../../../libs/common/src/dtos/activity.dto'), - ['../../../libs/common/src/dtos/announcement.dto']: await import('../../../libs/common/src/dtos/announcement.dto'), - }; - return { - '@nestjs/swagger': { - models: [ - [ - import('../../../libs/common/src/dtos/common.dto'), - { - DsnpUserIdParam: { userDsnpId: { required: true, type: () => String } }, - AnnouncementResponseDto: { referenceId: { required: true, type: () => String } }, - UploadResponseDto: { assetIds: { required: true, type: () => [String] } }, - FilesUploadDto: { files: { required: true, type: () => [Object] } }, - }, - ], - [ - import('../../../libs/common/src/dtos/activity.dto'), - { - LocationDto: { - name: { required: true, type: () => String, minLength: 1 }, - accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, - altitude: { required: false, type: () => Number }, - latitude: { required: false, type: () => Number }, - longitude: { required: false, type: () => Number }, - radius: { required: false, type: () => Number, minimum: 0 }, - units: { required: false, enum: t['../../../libs/common/src/dtos/activity.dto'].UnitTypeDto }, - }, - AssetReferenceDto: { - referenceId: { required: true, type: () => String, minLength: 1 }, - height: { required: false, type: () => Number, minimum: 1 }, - width: { required: false, type: () => Number, minimum: 1 }, - duration: { required: false, type: () => String, pattern: 'DURATION_REGEX' }, - }, - TagDto: { - type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].TagTypeDto }, - name: { required: false, type: () => String, minLength: 1 }, - mentionedId: { required: false, type: () => String, minLength: 1, pattern: 'DSNP_USER_URI_REGEX' }, - }, - AssetDto: { - type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].AttachmentTypeDto }, - references: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, - name: { required: false, type: () => String, minLength: 1 }, - href: { required: false, type: () => String, minLength: 1 }, - }, - BaseActivityDto: { - name: { required: false, type: () => String }, - tag: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].TagDto] }, - location: { required: false, type: () => t['../../../libs/common/src/dtos/activity.dto'].LocationDto }, - }, - NoteActivityDto: { - content: { required: true, type: () => String, minLength: 1 }, - published: { required: true, type: () => String, pattern: 'ISO8601_REGEX' }, - assets: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetDto] }, - }, - ProfileActivityDto: { - icon: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, - summary: { required: false, type: () => String }, - published: { required: false, type: () => String, pattern: 'ISO8601_REGEX' }, - }, - }, - ], - [ - import('../../../libs/common/src/dtos/announcement.dto'), - { - BroadcastDto: { content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto } }, - ReplyDto: { - inReplyTo: { required: true, type: () => String, pattern: 'DSNP_CONTENT_URI_REGEX' }, - content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, - }, - TombstoneDto: { - targetContentHash: { required: true, type: () => String, pattern: 'DSNP_CONTENT_HASH_REGEX' }, - targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, - }, - UpdateDto: { - targetContentHash: { required: true, type: () => String, pattern: 'DSNP_CONTENT_HASH_REGEX' }, - targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, - content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, - }, - ReactionDto: { - emoji: { required: true, type: () => String, minLength: 1, pattern: 'DSNP_EMOJI_REGEX' }, - apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, - inReplyTo: { required: true, type: () => String, pattern: 'DSNP_CONTENT_URI_REGEX' }, - }, - ProfileDto: { profile: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].ProfileActivityDto } }, - }, - ], - ], - controllers: [[import('./api.controller'), { ApiController: { health: {} } }]], - }, - }; -}; + const t = { + ["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"), + ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), + ["../../../libs/common/src/dtos/chain.watch.dto"]: await import("../../../libs/common/src/dtos/chain.watch.dto") + }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } }, "ResetScannerDto": { blockNumber: { required: true, type: () => Number } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/chain.watch.dto"), { "ChainWatchOptionsDto": { schemaIds: { required: true, type: () => [Number] }, dsnpIds: { required: true, type: () => [String] } } }], [import("../../../libs/common/src/dtos/request-job.dto"), { "ContentSearchRequestDto": { id: { required: true, type: () => String }, startBlock: { required: true, type: () => Number, minimum: 1 }, endBlock: { required: true, type: () => Number, minimum: 1 }, filters: { required: true, type: () => t["../../../libs/common/src/dtos/chain.watch.dto"].ChainWatchOptionsDto } } }], [import("../../../libs/common/src/dtos/subscription.webhook.dto"), { "WebhookRegistrationDto": { url: { required: true, type: () => String }, announcementTypes: { required: true, type: () => [String] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "resetScanner": { type: String }, "setWatchOptions": {}, "pauseScanner": {}, "startScanner": {}, "search": {}, "registerWebhook": {}, "clearAllWebHooks": {}, "getRegisteredWebhooks": {} } }]] } }; +}; \ No newline at end of file diff --git a/services/content-watcher/content-announcement.openapi.json b/services/content-watcher/content-announcement.openapi.json index 90f1381f..b88e030c 100644 --- a/services/content-watcher/content-announcement.openapi.json +++ b/services/content-watcher/content-announcement.openapi.json @@ -61,7 +61,7 @@ "description": "An optional identifier for the request, may be used for tracking or correlation" }, "schemaId": { - "type": "string", + "type": "integer", "description": "Identifier for the schema being used or referenced" }, "blockNumber": { diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts index facacf9c..94d62a67 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts @@ -5,11 +5,12 @@ https://docs.nestjs.com/modules import { Module } from '@nestjs/common'; import { BlockchainService } from './blockchain.service'; import { ConfigModule } from '../config/config.module'; +import { ChainEventProcessorService } from './chain-event-processor.service'; @Module({ imports: [ConfigModule], controllers: [], - providers: [BlockchainService], - exports: [BlockchainService], + providers: [BlockchainService, ChainEventProcessorService], + exports: [BlockchainService, ChainEventProcessorService], }) export class BlockchainModule {} diff --git a/services/content-watcher/libs/common/src/blockchain/chain-event-processor.service.ts b/services/content-watcher/libs/common/src/blockchain/chain-event-processor.service.ts new file mode 100644 index 00000000..803ce417 --- /dev/null +++ b/services/content-watcher/libs/common/src/blockchain/chain-event-processor.service.ts @@ -0,0 +1,111 @@ +import { Injectable } from '@nestjs/common'; +import { BlockchainService } from './blockchain.service'; +import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; +import { ApiDecoration } from '@polkadot/api/types'; +import { FrameSystemEventRecord } from '@polkadot/types/lookup'; +import { Vec } from '@polkadot/types'; +import { BlockPaginationResponseMessage, MessageResponse } from '@frequency-chain/api-augment/interfaces'; +import { MessageResponseWithSchemaId } from '../interfaces/message_response_with_schema_id'; +import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; +import { Queue } from 'bullmq'; + +@Injectable() +export class ChainEventProcessorService { + constructor(private readonly blockchainService: BlockchainService) {} + + public async getMessagesInBlock(blockNumber: number, filter?: ChainWatchOptionsDto): Promise { + const blockHash = await this.blockchainService.getBlockHash(blockNumber); + if (blockHash.isEmpty) { + return []; + } + const apiAt = await this.blockchainService.apiPromise.at(blockHash); + const events = await apiAt.query.system.events(); + return this.getMessagesFromEvents(apiAt, blockNumber, events, filter); + } + + private async getMessagesFromEvents( + apiAt: ApiDecoration<'promise'>, + blockNumber: number, + events: Vec, + filter?: ChainWatchOptionsDto, + ): Promise { + const hasMessages = events.some(({ event }) => apiAt.events.messages.MessagesInBlock.is(event)); + if (!hasMessages) { + return []; + } + + const keys = await apiAt.query.messages.messagesV2.keys(blockNumber); + let schemaIds = keys.map((key) => key.args[1].toNumber()); + schemaIds = Array.from(new Set(schemaIds)); + const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( + schemaIds.map(async (schemaId) => { + if (filter && filter?.schemaIds?.length > 0 && !filter.schemaIds.includes(schemaId)) { + return null; + } + let paginationRequest = { + from_block: blockNumber, + from_index: 0, + page_size: 1000, + to_block: blockNumber + 1, + }; + + let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + const messages: Vec = messageResponse.content; + while (messageResponse.has_next.toHuman()) { + paginationRequest = { + from_block: blockNumber, + from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, + page_size: 1000, + to_block: blockNumber + 1, + }; + // eslint-disable-next-line no-await-in-loop + messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); + if (messageResponse.content.length > 0) { + messages.push(...messageResponse.content); + } + } + const messagesWithSchemaId: MessageResponseWithSchemaId = { + schemaId: schemaId, + messages, + }; + return messagesWithSchemaId; + }), + ); + const collectedMessages: MessageResponseWithSchemaId[] = []; + filteredEvents.forEach((event) => { + if (event) { + collectedMessages.push(event); + } + }); + return collectedMessages; + } + + public async queueIPFSJobs(messages: MessageResponseWithSchemaId[], queue: Queue, requestId?: string): Promise { + const jobs = messages.flatMap((messageResponse) => + messageResponse.messages + .filter((message) => message.cid && message.cid.isSome) + .map((message) => { + const job = createIPFSQueueJob( + message.block_number.toNumber(), + message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), + message.provider_msa_id.toString(), + messageResponse.schemaId, + message.cid.unwrap().toString(), + message.index.toNumber(), + requestId + ); + + return { + name: `IPFS Job: ${job.key}`, + data: job.data, + opts: { jobId: job.key }, + }; + }), + ); + + if (jobs && jobs.length > 0) { + await queue.addBulk(jobs); + } + } + +} diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index dcadb431..fa278dfe 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -3,19 +3,13 @@ import { Injectable } from '@nestjs/common'; import { InjectQueue, Processor } from '@nestjs/bullmq'; import Redis from 'ioredis'; import { InjectRedis } from '@songkeys/nestjs-redis'; -import { Vec } from '@polkadot/types'; -import { BlockPaginationResponseMessage, MessageResponse, SchemaId } from '@frequency-chain/api-augment/interfaces'; import { Job, Queue } from 'bullmq'; -import { BlockNumber } from '@polkadot/types/interfaces'; -import { FrameSystemEventRecord } from '@polkadot/types/lookup'; -import { BlockchainService } from '../blockchain/blockchain.service'; import * as QueueConstants from '../utils/queues'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; -import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { ContentSearchRequestDto } from '../dtos/request-job.dto'; import { REGISTERED_WEBHOOK_KEY } from '../constants'; -import { MessageResponseWithSchemaId } from '../interfaces/message_response_with_schema_id'; +import { ChainEventProcessorService } from '../blockchain/chain-event-processor.service'; @Injectable() @Processor(QueueConstants.REQUEST_QUEUE_NAME, { @@ -23,136 +17,37 @@ import { MessageResponseWithSchemaId } from '../interfaces/message_response_with }) export class CrawlerService extends BaseConsumer { constructor( - private readonly blockchainService: BlockchainService, @InjectRedis() private readonly cache: Redis, @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, + private readonly chainEventService: ChainEventProcessorService, ) { super(); } - async process(job: Job): Promise { + async process(job: Job): Promise { this.logger.log(`Processing crawler job ${job.id}`); const registeredWebhook = await this.cache.get(REGISTERED_WEBHOOK_KEY); if (!registeredWebhook) { throw new Error('No registered webhook to send data to'); } - const blockList: bigint[] = []; - const blockStart = BigInt(job.data.startBlock); - const blockEnd = BigInt(job.data.endBlock); - for (let i = blockStart; i <= blockEnd; i += 1n) { - blockList.push(BigInt(i)); + const blockList: number[] = []; + for (let i = job.data.startBlock; i <= job.data.endBlock; i += 1) { + blockList.push(i); } await this.processBlockList(job.data.id, blockList, job.data.filters); this.logger.log(`Finished processing job ${job.id}`); } - private async processBlockList(id: string, blockList: bigint[], filters: ChainWatchOptionsDto) { - const promises: Promise[] = []; - - blockList.forEach(async (blockNumber) => { - const latestBlockHash = await this.blockchainService.getBlockHash(blockNumber); - if (latestBlockHash.isEmpty) { - return; - } - // eslint-disable-next-line no-await-in-loop - const events = await this.fetchEventsFromBlockchain(latestBlockHash); - this.logger.debug(`Processing ${events.length} events for block ${blockNumber}`); - - // eslint-disable-next-line no-await-in-loop - const messages = await this.processEvents(events, filters); + private async processBlockList(id: string, blockList: number[], filters: ChainWatchOptionsDto) { + await Promise.all(blockList.map(async (blockNumber) => { + const messages = await this.chainEventService.getMessagesInBlock(blockNumber, filters); if (messages.length > 0) { this.logger.debug(`Found ${messages.length} messages for block ${blockNumber}`); } // eslint-disable-next-line no-await-in-loop - await this.queueIPFSJobs(id, messages); - - promises.push(Promise.resolve()); - }); - - await Promise.all(promises); - } - - private async fetchEventsFromBlockchain(latestBlockHash: any) { - return (await this.blockchainService.queryAt(latestBlockHash, 'system', 'events')).toArray(); - } - - private async processEvents(events: Vec, eventsToWatch: ChainWatchOptionsDto): Promise { - const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( - events.map(async (event) => { - if (event.event.section === 'messages' && event.event.method === 'MessagesStored') { - if (eventsToWatch?.schemaIds?.length > 0 && !eventsToWatch.schemaIds.includes(event.event.data[0]?.toString())) { - return null; - } - const schemaId = event.event.data[0] as SchemaId; - const blockNumber = event.event.data[1] as BlockNumber; - let paginationRequest = { - from_block: blockNumber.toBigInt(), - from_index: 0, - page_size: 1000, - to_block: blockNumber.toBigInt() + 1n, - }; - - let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - const messages: Vec = messageResponse.content; - while (messageResponse.has_next.toHuman()) { - paginationRequest = { - from_block: blockNumber.toBigInt(), - from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, - page_size: 1000, - to_block: blockNumber.toBigInt() + 1n, - }; - // eslint-disable-next-line no-await-in-loop - messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - if (messageResponse.content.length > 0) { - messages.push(...messageResponse.content); - } - } - const messagesWithSchemaId: MessageResponseWithSchemaId = { - schemaId: schemaId.toString(), - messages, - }; - return messagesWithSchemaId; - } - return null; - }), - ); - const collectedMessages: MessageResponseWithSchemaId[] = []; - filteredEvents.forEach((event) => { - if (event) { - collectedMessages.push(event); - } - }); - - return collectedMessages; - } - - private async queueIPFSJobs(requestId: string, messages: MessageResponseWithSchemaId[]): Promise { - const promises = messages.map(async (messageResponse) => { - const { schemaId } = messageResponse; - const innerPromises = messageResponse.messages.map(async (message) => { - if (!message.cid || message.cid.isNone) { - return; - } - - const ipfsQueueJob = createIPFSQueueJob( - message.block_number.toNumber(), - message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), - message.provider_msa_id.toString(), - schemaId, - message.cid.unwrap().toString(), - message.index.toNumber(), - requestId, - ); - // eslint-disable-next-line no-await-in-loop - await this.ipfsQueue.add(`IPFS Job: ${ipfsQueueJob.key}`, ipfsQueueJob.data, { jobId: ipfsQueueJob.key }); - }); - - // eslint-disable-next-line no-await-in-loop - await Promise.all(innerPromises); - }); - - await Promise.all(promises); + await this.chainEventService.queueIPFSJobs(messages, this.ipfsQueue, id); + })); } } diff --git a/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts index b374faa5..900a9da0 100644 --- a/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts @@ -5,19 +5,23 @@ import { IsArray, IsOptional } from 'class-validator'; /** * Interface for chain filter options * @interface IChainWatchOptions - * @property {string[]} schemaIds - The schema ids for which content should be watched for + * @property {number[]} schemaIds - The schema ids for which content should be watched for * @property {string[]} msa_ids - The msa ids for which content should be watched for */ export class ChainWatchOptionsDto { // Specific schema ids to watch for @IsOptional() @IsArray() - @Type(() => String) + @Type(() => Number) @ApiProperty({ + type: 'array', + items: { + type: 'number', + }, description: 'Specific schema ids to watch for', - example: ['1', '19'], + example: [1, 19], }) - schemaIds: string[]; + schemaIds: number[]; // Specific dsnpIds (msa_id) to watch for @IsOptional() diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index de725582..dc2f27ad 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -25,8 +25,8 @@ export class FilesUploadDto { } export class ResetScannerDto { - @ApiProperty({ type: 'string', description: 'The block number to reset the scanner to', example: '0' }) - blockNumber: string; + @ApiProperty({ type: 'number', description: 'The block number to reset the scanner to', example: 0 }) + blockNumber: number; } // eslint-disable-next-line no-shadow diff --git a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts index 6cbb32d6..b950ad19 100644 --- a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts @@ -1,4 +1,4 @@ -import { IsArray, IsOptional, IsString } from 'class-validator'; +import { IsInt, IsOptional, IsPositive, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { ChainWatchOptionsDto } from './chain.watch.dto'; @@ -7,19 +7,21 @@ export class ContentSearchRequestDto { @IsString() id: string; - @IsString() + @IsInt() + @IsPositive() @ApiProperty({ description: 'The starting block number to search from', - example: '100', + example: 100, }) - startBlock: string; + startBlock: number; - @IsString() + @IsInt() + @IsPositive() @ApiProperty({ description: 'The ending block number to search to', - example: '101', + example: 101, }) - endBlock: string; + endBlock: number; @IsOptional() @ApiProperty({ diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index 6079203a..9df9b367 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -1,7 +1,7 @@ export interface IIPFSJob { msaId: string; providerId: string; - schemaId: string; + schemaId: number; cid: string; blockNumber: number; index: number; @@ -12,10 +12,10 @@ export function createIPFSQueueJob( blockNumber: number, msaId: string, providerId: string, - schemaId: string, + schemaId: number, cid: string, index: number, - requestId: string, + requestId?: string, ): { key: string; data: IIPFSJob } { return { key: `${msaId}:${providerId}:${index}:${requestId}`, diff --git a/services/content-watcher/libs/common/src/interfaces/message_response_with_schema_id.ts b/services/content-watcher/libs/common/src/interfaces/message_response_with_schema_id.ts index 6059e7df..28058b4b 100644 --- a/services/content-watcher/libs/common/src/interfaces/message_response_with_schema_id.ts +++ b/services/content-watcher/libs/common/src/interfaces/message_response_with_schema_id.ts @@ -2,6 +2,6 @@ import { MessageResponse } from '@frequency-chain/api-augment/interfaces'; import { Vec } from '@polkadot/types'; export interface MessageResponseWithSchemaId { - schemaId: string; + schemaId: number; messages: Vec; } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 781df07e..25797d1e 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -1,24 +1,20 @@ /* eslint-disable no-underscore-dangle */ +/* eslint-disable no-await-in-loop */ import '@frequency-chain/api-augment'; -import { BlockPaginationResponseMessage, MessageResponse } from '@frequency-chain/api-augment/interfaces'; import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import Redis from 'ioredis'; import { InjectRedis } from '@songkeys/nestjs-redis'; import { SchedulerRegistry } from '@nestjs/schedule'; import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; -import { Vec } from '@polkadot/types'; import { Queue } from 'bullmq'; -import { FrameSystemEventRecord } from '@polkadot/types/lookup'; import { ConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; import * as QueueConstants from '../utils/queues'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; -import { createIPFSQueueJob } from '../interfaces/ipfs.job.interface'; import * as RedisUtils from '../utils/redis'; -import { MessageResponseWithSchemaId } from '../interfaces/message_response_with_schema_id'; -import { ApiDecoration } from '@polkadot/api/types'; +import { ChainEventProcessorService } from '../blockchain/chain-event-processor.service'; @Injectable() export class ScannerService implements OnApplicationBootstrap { @@ -34,6 +30,7 @@ export class ScannerService implements OnApplicationBootstrap { @InjectRedis() private readonly cache: Redis, @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, private schedulerRegistry: SchedulerRegistry, + private chainEventProcessor: ChainEventProcessorService, ) { this.logger = new Logger(ScannerService.name); } @@ -98,36 +95,27 @@ export class ScannerService implements OnApplicationBootstrap { this.scanInProgress = true; let lastScannedBlock = await this.getLastSeenBlockNumber(); const currentBlockNumber = lastScannedBlock + 1; - let latestBlockHash = await this.blockchainService.getBlockHash(currentBlockNumber); + let currentBlockHash = await this.blockchainService.getBlockHash(currentBlockNumber); - if (!latestBlockHash.some((byte) => byte !== 0)) { + if (currentBlockHash.isEmpty) { this.logger.log('No new blocks to read; no scan performed.'); this.scanInProgress = false; return; } - this.logger.log(`Starting scan from block #${currentBlockNumber} (${latestBlockHash})`); - - while (!this.paused && !latestBlockHash.isEmpty && queueSize < this.configService.queueHighWater) { - // eslint-disable-next-line no-await-in-loop - const at = await this.blockchainService.apiPromise.at(latestBlockHash.toHex()); - // eslint-disable-next-line no-await-in-loop - const events = await at.query.system.events(); - // eslint-disable-next-line no-await-in-loop - const messages = await this.processEvents(at, lastScannedBlock, events, eventsToWatch); + this.logger.log(`Starting scan from block #${currentBlockNumber} (${currentBlockHash})`); + + while (!this.paused && !currentBlockHash.isEmpty && queueSize < this.configService.queueHighWater) { + const messages = await this.chainEventProcessor.getMessagesInBlock(lastScannedBlock, eventsToWatch); if (messages.length > 0) { this.logger.debug(`Found ${messages.length} messages to process`); } - // eslint-disable-next-line no-await-in-loop - await this.queueIPFSJobs(messages); - // eslint-disable-next-line no-await-in-loop + await this.chainEventProcessor.queueIPFSJobs(messages, this.ipfsQueue); await this.saveProgress(lastScannedBlock); lastScannedBlock += 1; - // eslint-disable-next-line no-await-in-loop - latestBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); - // eslint-disable-next-line no-await-in-loop + currentBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); queueSize = await this.ipfsQueue.count(); } - if (latestBlockHash.isEmpty) { + if (currentBlockHash.isEmpty) { this.logger.log(`Scan reached end-of-chain at block ${lastScannedBlock - 1}`); } else if (queueSize > this.configService.queueHighWater) { this.logger.log('Queue soft limit reached; pausing scan until next iteration'); @@ -139,91 +127,6 @@ export class ScannerService implements OnApplicationBootstrap { } } - private async processEvents( - apiAt: ApiDecoration<'promise'>, - blockNumber: number, - events: Vec, - eventsToWatch: ChainWatchOptionsDto, - ): Promise { - const hasMessages = events.some(({ event }) => apiAt.events.messages.MessagesInBlock.is(event)); - if (!hasMessages) { - return []; - } - - const keys = await apiAt.query.messages.messagesV2.keys(blockNumber); - let schemaIds = keys.map((key) => key.args[1].toString()); - schemaIds = Array.from(new Set(...schemaIds)); - const filteredEvents: (MessageResponseWithSchemaId | null)[] = await Promise.all( - schemaIds.map(async (schemaId) => { - if (eventsToWatch?.schemaIds?.length > 0 && !eventsToWatch.schemaIds.includes(schemaId)) { - return null; - } - let paginationRequest = { - from_block: blockNumber, - from_index: 0, - page_size: 1000, - to_block: blockNumber + 1, - }; - - let messageResponse: BlockPaginationResponseMessage = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - const messages: Vec = messageResponse.content; - while (messageResponse.has_next.toHuman()) { - paginationRequest = { - from_block: blockNumber, - from_index: messageResponse.next_index.isSome ? messageResponse.next_index.unwrap().toNumber() : 0, - page_size: 1000, - to_block: blockNumber + 1, - }; - // eslint-disable-next-line no-await-in-loop - messageResponse = await this.blockchainService.apiPromise.rpc.messages.getBySchemaId(schemaId, paginationRequest); - if (messageResponse.content.length > 0) { - messages.push(...messageResponse.content); - } - } - const messagesWithSchemaId: MessageResponseWithSchemaId = { - schemaId: schemaId.toString(), - messages, - }; - return messagesWithSchemaId; - }), - ); - const collectedMessages: MessageResponseWithSchemaId[] = []; - filteredEvents.forEach((event) => { - if (event) { - collectedMessages.push(event); - } - }); - return collectedMessages; - } - - private async queueIPFSJobs(messages: MessageResponseWithSchemaId[]): Promise { - const jobs = messages.flatMap((messageResponse) => - messageResponse.messages - .filter((message) => message.cid && message.cid.isSome) - .map((message) => { - const job = createIPFSQueueJob( - message.block_number.toNumber(), - message.msa_id.isNone ? message.provider_msa_id.toString() : message.msa_id.unwrap().toString(), - message.provider_msa_id.toString(), - messageResponse.schemaId, - message.cid.unwrap().toString(), - message.index.toNumber(), - '', - ); - - return { - name: `IPFS Job: ${job.key}`, - data: job.data, - opts: { jobId: job.key }, - }; - }), - ); - - if (jobs && jobs.length > 0) { - await this.ipfsQueue.addBulk(jobs); - } - } - private async getLastSeenBlockNumber(): Promise { return Number((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0); } diff --git a/services/content-watcher/libs/common/src/types/content-announcement/index.ts b/services/content-watcher/libs/common/src/types/content-announcement/index.ts index 343d3c8f..56bade12 100644 --- a/services/content-watcher/libs/common/src/types/content-announcement/index.ts +++ b/services/content-watcher/libs/common/src/types/content-announcement/index.ts @@ -1,2 +1,2 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; +export * from './types.gen'; \ No newline at end of file diff --git a/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts b/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts index cae2fdb1..eb3b3f93 100644 --- a/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts +++ b/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts @@ -1,66 +1,66 @@ // This file is auto-generated by @hey-api/openapi-ts export enum AnnouncementType { - Tombstone = 0, - Broadcast = 2, - Reply = 3, - Reaction = 4, - Profile = 5, - Update = 6, - PublicFollows = 113, + Tombstone = 0, + Broadcast = 2, + Reply = 3, + Reaction = 4, + Profile = 5, + Update = 6, + PublicFollows = 113 } export type AnnouncementResponse = { - /** - * An optional identifier for the request, may be used for tracking or correlation - */ - requestId?: string | null; - /** - * Identifier for the schema being used or referenced - */ - schemaId: string; - /** - * The block number on the blockchain where this announcement was recorded - */ - blockNumber: number; - announcement: TombstoneAnnouncement | BroadcastAnnouncement | ReplyAnnouncement | ReactionAnnouncement | ProfileAnnouncement | UpdateAnnouncement; + /** + * An optional identifier for the request, may be used for tracking or correlation + */ + requestId?: string | null; + /** + * Identifier for the schema being used or referenced + */ + schemaId: number; + /** + * The block number on the blockchain where this announcement was recorded + */ + blockNumber: number; + announcement: TombstoneAnnouncement | BroadcastAnnouncement | ReplyAnnouncement | ReactionAnnouncement | ProfileAnnouncement | UpdateAnnouncement; }; export type TypedAnnouncement = { - announcementType: AnnouncementType; - fromId: string; + announcementType: AnnouncementType; + fromId: string; }; export type TombstoneAnnouncement = TypedAnnouncement & { - targetAnnouncementType: number; - targetContentHash: string; + targetAnnouncementType: number; + targetContentHash: string; }; export type BroadcastAnnouncement = TypedAnnouncement & { - contentHash: string; - url: string; + contentHash: string; + url: string; }; export type ReplyAnnouncement = TypedAnnouncement & { - contentHash: string; - inReplyTo: string; - url: string; + contentHash: string; + inReplyTo: string; + url: string; }; export type ReactionAnnouncement = TypedAnnouncement & { - emoji: string; - inReplyTo: string; - apply: number; + emoji: string; + inReplyTo: string; + apply: number; }; export type ProfileAnnouncement = TypedAnnouncement & { - contentHash: string; - url: string; + contentHash: string; + url: string; }; export type UpdateAnnouncement = TypedAnnouncement & { - contentHash: string; - targetAnnouncementType: number; - targetContentHash: string; - url: string; -}; + contentHash: string; + targetAnnouncementType: number; + targetContentHash: string; + url: string; +}; \ No newline at end of file From 9bd55945b49d710f5959b8811b536a04271d6366 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Thu, 23 May 2024 16:39:21 -0400 Subject: [PATCH 128/137] make reset/pause/restart scan work correctly (#61) # Description Makes changing the current block number while the scan is paused work correctly. * Adds additional request options to `resetScan` endpoint: * `rewindOffset`: number; indicates number of blocks to rewind from either end of chain or block number in request * `immediate`: boolean; indicates whether to start the scan immediately from the new position, or allow it to take effect at the next scheduled interval Refactors the scan loop a bit to make more compact & readable, as well as to be able to honor scan reset requests even if a scan is in-progress. --- .../apps/api/src/api.controller.ts | 17 ++- .../apps/api/src/api.service.ts | 20 +-- .../src/blockchain/blockchain.service.ts | 4 +- .../libs/common/src/config/config.service.ts | 4 +- .../libs/common/src/config/env.config.ts | 2 +- .../libs/common/src/dtos/announcement.dto.ts | 2 +- .../libs/common/src/dtos/common.dto.ts | 22 +++- .../src/interfaces/scan-reset.interface.ts | 5 + .../libs/common/src/scanner/scanner.ts | 120 ++++++++++-------- 9 files changed, 120 insertions(+), 76 deletions(-) create mode 100644 services/content-watcher/libs/common/src/interfaces/scan-reset.interface.ts diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts index 8dfa8970..630c4a36 100644 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ b/services/content-watcher/apps/api/src/api.controller.ts @@ -1,5 +1,5 @@ -import { Body, Controller, Delete, Get, HttpStatus, Logger, Post, Put } from '@nestjs/common'; -import { ApiBody, ApiOkResponse } from '@nestjs/swagger'; +import { Body, Controller, Delete, Get, HttpStatus, Logger, Post, Put, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiQuery } from '@nestjs/swagger'; import { ApiService } from './api.service'; import { ResetScannerDto, ContentSearchRequestDto } from '../../../libs/common/src'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; @@ -27,7 +27,7 @@ export class ApiController { type: ResetScannerDto, }) resetScanner(@Body() resetScannerDto: ResetScannerDto) { - return this.apiService.setLastSeenBlockNumber(BigInt(resetScannerDto.blockNumber ?? 0n)); + return this.apiService.resetScanner(resetScannerDto); } @Post('setWatchOptions') @@ -45,8 +45,15 @@ export class ApiController { } @Post('startScanner') - startScanner() { - return this.apiService.resumeScanner(); + @ApiQuery({ + name: 'immediate', + description: 'immediate: whether to resume scan immediately (true), or wait until next scheduled scan (false)', + type: 'boolean', + required: false, + }) + startScanner(@Query('immediate') immediate?: boolean) { + this.logger.debug(`Resuming scan; immediate=${immediate}`); + return this.apiService.resumeScanner(immediate); } @Put('search') diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index eee2396b..3395d7c1 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -3,12 +3,13 @@ import { InjectRedis } from '@songkeys/nestjs-redis'; import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; -import { ContentSearchRequestDto, REQUEST_QUEUE_NAME, calculateJobId } from '../../../libs/common/src'; +import { ContentSearchRequestDto, REQUEST_QUEUE_NAME, ResetScannerDto, calculateJobId } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner'; -import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../../../libs/common/src/constants'; +import { EVENTS_TO_WATCH_KEY, REGISTERED_WEBHOOK_KEY } from '../../../libs/common/src/constants'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; import { WebhookRegistrationDto } from '../../../libs/common/src/dtos/subscription.webhook.dto'; import * as RedisUtils from '../../../libs/common/src/utils/redis'; +import { IScanReset } from '../../../libs/common/src/interfaces/scan-reset.interface'; @Injectable() export class ApiService { @@ -22,11 +23,6 @@ export class ApiService { this.logger = new Logger(this.constructor.name); } - public setLastSeenBlockNumber(blockNumber: bigint) { - this.logger.warn(`Setting last seen block number to ${blockNumber}`); - return this.redis.setex(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, blockNumber.toString()); - } - public async setWatchOptions(watchOptions: ChainWatchOptionsDto) { this.logger.warn(`Setting watch options to ${JSON.stringify(watchOptions)}`); const currentWatchOptions = await this.redis.get(EVENTS_TO_WATCH_KEY); @@ -36,12 +32,16 @@ export class ApiService { public pauseScanner() { this.logger.warn('Pausing scanner'); - return this.scannerService.pauseScanner(); + this.scannerService.pauseScanner(); } - public resumeScanner() { + public resumeScanner(immediate = false) { this.logger.warn('Resuming scanner'); - return this.scannerService.resumeScanner(); + this.scannerService.resumeScanner(immediate); + } + + public async resetScanner(resetScannerOptions: IScanReset) { + await this.scannerService.resetScan(resetScannerOptions); } public async searchContent(contentSearchRequestDto: ContentSearchRequestDto) { diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index c75b4cf1..c205c555 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -71,8 +71,8 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS return (await this.apiPromise.rpc.chain.getFinalizedHead()) as BlockHash; } - public async getLatestFinalizedBlockNumber(): Promise { - return (await this.apiPromise.rpc.chain.getBlock()).block.header.number.toBigInt(); + public async getLatestFinalizedBlockNumber(): Promise { + return (await this.apiPromise.rpc.chain.getBlock()).block.header.number.toNumber(); } public async getBlockNumberForHash(hash: string): Promise { diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index df3c8861..63a26a48 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -37,8 +37,8 @@ export class ConfigService { return this.nestConfigService.get('FREQUENCY_URL')!; } - public get startingBlock(): string { - return this.nestConfigService.get('STARTING_BLOCK')!; + public get startingBlock(): number | undefined { + return this.nestConfigService.get('STARTING_BLOCK')!; } public get blockchainScanIntervalMinutes(): number { diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index c6ecb3ac..90feb804 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -10,7 +10,7 @@ export const configModuleOptions: ConfigModuleOptions = { IPFS_BASIC_AUTH_SECRET: Joi.string().allow('').default(''), REDIS_URL: Joi.string().uri().required(), FREQUENCY_URL: Joi.string().uri().required(), - STARTING_BLOCK: Joi.number().min(1).default(1), + STARTING_BLOCK: Joi.number().min(1), BLOCKCHAIN_SCAN_INTERVAL_MINUTES: Joi.number() .min(1) .default(3 * 60), diff --git a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts index 7f990f77..f139e985 100644 --- a/services/content-watcher/libs/common/src/dtos/announcement.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/announcement.dto.ts @@ -2,7 +2,7 @@ * File name should always end with `.dto.ts` for swagger metadata generator to get picked up */ // eslint-disable-next-line max-classes-per-file -import { IsEnum, IsHexadecimal, IsInt, IsNotEmpty, IsString, Matches, Max, MaxLength, Min, MinLength, ValidateNested } from 'class-validator'; +import { IsEnum, IsInt, IsNotEmpty, IsString, Matches, Max, Min, MinLength, ValidateNested } from 'class-validator'; import { Type } from 'class-transformer'; import { NoteActivityDto, ProfileActivityDto } from './activity.dto'; import { DSNP_CONTENT_HASH_REGEX, DSNP_CONTENT_URI_REGEX, DSNP_EMOJI_REGEX } from './validation.dto'; diff --git a/services/content-watcher/libs/common/src/dtos/common.dto.ts b/services/content-watcher/libs/common/src/dtos/common.dto.ts index dc2f27ad..57c895e4 100644 --- a/services/content-watcher/libs/common/src/dtos/common.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/common.dto.ts @@ -3,7 +3,8 @@ */ // eslint-disable-next-line max-classes-per-file import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsNumberString } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsNumberString, IsOptional, IsPositive, isPositive } from 'class-validator'; +import { IScanReset } from '../interfaces/scan-reset.interface'; export class DsnpUserIdParam { @IsNotEmpty() @@ -24,9 +25,22 @@ export class FilesUploadDto { files: any[]; } -export class ResetScannerDto { - @ApiProperty({ type: 'number', description: 'The block number to reset the scanner to', example: 0 }) - blockNumber: number; +export class ResetScannerDto implements IScanReset { + @IsOptional() + @IsNumber() + @IsPositive() + @ApiProperty({ required: false, type: 'number', description: 'The block number to reset the scanner to', example: 0 }) + blockNumber?: number; + + @IsOptional() + @IsNumber() + @ApiProperty({ required: false, type: 'number', description: 'Number of blocks to rewind the scanner to (from `blockNumber` if supplied; else from latest block', example: 100 }) + rewindOffset?: number; + + @IsOptional() + @IsBoolean() + @ApiProperty({ required: false, type: 'boolean', description: 'Whether to schedule the new scan immediately or wait for the next scheduled interval', example: true }) + immediate?: boolean; } // eslint-disable-next-line no-shadow diff --git a/services/content-watcher/libs/common/src/interfaces/scan-reset.interface.ts b/services/content-watcher/libs/common/src/interfaces/scan-reset.interface.ts new file mode 100644 index 00000000..01438395 --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/scan-reset.interface.ts @@ -0,0 +1,5 @@ +export interface IScanReset { + blockNumber?: number; + rewindOffset?: number; + immediate?: boolean; +} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 25797d1e..3b807c81 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -1,7 +1,7 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable no-await-in-loop */ import '@frequency-chain/api-augment'; -import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { Injectable, Logger, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import Redis from 'ioredis'; import { InjectRedis } from '@songkeys/nestjs-redis'; @@ -15,14 +15,17 @@ import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEB import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import * as RedisUtils from '../utils/redis'; import { ChainEventProcessorService } from '../blockchain/chain-event-processor.service'; +import { IScanReset } from '../interfaces/scan-reset.interface'; + +const INTERVAL_SCAN_NAME = 'intervalScan'; @Injectable() -export class ScannerService implements OnApplicationBootstrap { +export class ScannerService implements OnApplicationBootstrap, OnApplicationShutdown { private readonly logger: Logger; private scanInProgress = false; - private paused = false; + private scanResetBlockNumber: number | undefined; constructor( private readonly configService: ConfigService, @@ -36,32 +39,43 @@ export class ScannerService implements OnApplicationBootstrap { } async onApplicationBootstrap() { - const startingBlock = Number(this.configService.startingBlock) - 1; - this.setLastSeenBlockNumber(startingBlock); - this.scheduleInitialScan(); - this.scheduleBlockchainScan(); - } - - private scheduleInitialScan() { - const initialTimeout = setTimeout(() => this.scan(), 0); - this.schedulerRegistry.addTimeout('initialScan', initialTimeout); - } + const startingBlock = this.configService.startingBlock; + if (startingBlock) { + this.logger.log(`Setting initial scan block to ${startingBlock}`); + this.setLastSeenBlockNumber(startingBlock - 1); + } + setImmediate(() => this.scan()); - private scheduleBlockchainScan() { const scanInterval = this.configService.blockchainScanIntervalMinutes * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; + this.schedulerRegistry.addInterval(INTERVAL_SCAN_NAME, setInterval(() => this.scan(), scanInterval)); + } - const interval = setInterval(() => this.scan(), scanInterval); - this.schedulerRegistry.addInterval('blockchainScan', interval); + onApplicationShutdown(_signal?: string | undefined) { + const interval = this.schedulerRegistry.getInterval(INTERVAL_SCAN_NAME); + clearInterval(interval); } - public async pauseScanner() { + public pauseScanner() { this.logger.debug('Pausing scanner'); this.paused = true; } - public async resumeScanner() { + public resumeScanner(immediate = false) { this.logger.debug('Resuming scanner'); this.paused = false; + if (immediate) { + setImmediate(() => this.scan()); + } + } + + public async resetScan({ blockNumber, rewindOffset, immediate }: IScanReset) { + this.pauseScanner(); + let targetBlock = blockNumber ?? await this.blockchainService.getLatestFinalizedBlockNumber(); + targetBlock -= rewindOffset ? Math.abs(rewindOffset) : 0; + targetBlock = Math.max(targetBlock, 1); + this.scanResetBlockNumber = targetBlock; + this.logger.log(`Resetting scan to block #${targetBlock}`); + this.resumeScanner(immediate); } async scan() { @@ -73,18 +87,7 @@ export class ScannerService implements OnApplicationBootstrap { return; } - if (this.paused) { - this.logger.debug('Scanner is paused'); - return; - } - let queueSize = await this.ipfsQueue.count(); - - if (queueSize > 0) { - this.logger.log('Deferring next blockchain scan until queue is empty'); - return; - } const registeredWebhook = await this.cache.get(REGISTERED_WEBHOOK_KEY); - if (!registeredWebhook) { this.logger.log('No registered webhooks; no scan performed.'); return; @@ -93,32 +96,38 @@ export class ScannerService implements OnApplicationBootstrap { const eventsToWatch: ChainWatchOptionsDto = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; this.scanInProgress = true; - let lastScannedBlock = await this.getLastSeenBlockNumber(); - const currentBlockNumber = lastScannedBlock + 1; - let currentBlockHash = await this.blockchainService.getBlockHash(currentBlockNumber); - if (currentBlockHash.isEmpty) { - this.logger.log('No new blocks to read; no scan performed.'); - this.scanInProgress = false; - return; - } - this.logger.log(`Starting scan from block #${currentBlockNumber} (${currentBlockHash})`); + let first = true; + while (true) { + if (this.paused) { + this.logger.log('Scan paused'); + break; + } + + const queueSize = await this.ipfsQueue.count(); + if (queueSize > this.configService.queueHighWater) { + this.logger.log('Queue soft limit reached; pausing scan until next interval'); + break; + } - while (!this.paused && !currentBlockHash.isEmpty && queueSize < this.configService.queueHighWater) { - const messages = await this.chainEventProcessor.getMessagesInBlock(lastScannedBlock, eventsToWatch); + const currentBlockNumber = await this.getNextBlockNumber(); + const currentBlockHash = await this.blockchainService.getBlockHash(currentBlockNumber); + if (currentBlockHash.isEmpty) { + this.logger.log(`No new blocks to scan @ block number ${currentBlockNumber}; pausing scan until next interval`); + break; + } + + if (first) { + this.logger.log(`Starting scan @ block # ${currentBlockNumber} (${currentBlockHash})`); + first = false; + } + + const messages = await this.chainEventProcessor.getMessagesInBlock(currentBlockNumber, eventsToWatch); if (messages.length > 0) { this.logger.debug(`Found ${messages.length} messages to process`); } await this.chainEventProcessor.queueIPFSJobs(messages, this.ipfsQueue); - await this.saveProgress(lastScannedBlock); - lastScannedBlock += 1; - currentBlockHash = await this.blockchainService.getBlockHash(lastScannedBlock); - queueSize = await this.ipfsQueue.count(); - } - if (currentBlockHash.isEmpty) { - this.logger.log(`Scan reached end-of-chain at block ${lastScannedBlock - 1}`); - } else if (queueSize > this.configService.queueHighWater) { - this.logger.log('Queue soft limit reached; pausing scan until next iteration'); + await this.saveProgress(currentBlockNumber); } } catch (err) { this.logger.error(err); @@ -127,8 +136,17 @@ export class ScannerService implements OnApplicationBootstrap { } } - private async getLastSeenBlockNumber(): Promise { - return Number((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0); + private async getNextBlockNumber(): Promise { + let nextBlock: number; + if (this.scanResetBlockNumber) { + await this.setLastSeenBlockNumber(this.scanResetBlockNumber - 1); + nextBlock = this.scanResetBlockNumber; + this.scanResetBlockNumber = undefined; + } else { + nextBlock = (Number(await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0) + 1; + } + + return nextBlock; } private async saveProgress(blockNumber: number): Promise { From ac85acc13ea8967ecc27986a3d2c03b93b7dd159 Mon Sep 17 00:00:00 2001 From: Wil Wade Date: Thu, 20 Jun 2024 12:53:50 -0400 Subject: [PATCH 129/137] Update Readme and standardize (#75) Updating the readme to the standard as well as standardizing a bunch of other things. - Readme updates - e2e tests now use the app running in docker - All setup scripts should work again - Add webhook client utility to easily see the output of the webhook - Jest unit tests uses the env.template instead of .env - Dockerfiles updated to standards - Formatting Closes #32 --- services/content-watcher/.dockerignore | 40 +- .../.env.content-publishing-service | 12 + services/content-watcher/.env.dev | 19 - services/content-watcher/.env.docker.dev | 51 ++- services/content-watcher/.gitignore | 5 +- services/content-watcher/Dockerfile | 75 +++- services/content-watcher/ENVIRONMENT.md | 28 +- services/content-watcher/INSTALLING.md | 2 +- services/content-watcher/README.md | 360 +++++++++++++++--- .../apps/api/src/api.service.ts | 2 +- .../apps/api/test/app.e2e-spec.ts | 67 ++-- services/content-watcher/dev.Dockerfile | 19 +- services/content-watcher/docker-compose.yaml | 12 +- services/content-watcher/docs/.gitkeep | 0 .../docs/content_watcher_service_arch.drawio | 208 ++++++++++ .../content_watcher_service_arch.drawio.png | Bin 0 -> 286970 bytes services/content-watcher/env.template | 8 +- services/content-watcher/jest-setup.ts | 2 + services/content-watcher/package-lock.json | 26 +- services/content-watcher/package.json | 37 +- .../scripts/chain-setup/local-chain-setup.cjs | 116 +++--- .../scripts/content-setup/index.js | 92 ++--- services/content-watcher/scripts/local-cw.sh | 2 +- .../content-watcher/scripts/webhook-cat.cjs | 34 ++ 24 files changed, 907 insertions(+), 310 deletions(-) create mode 100644 services/content-watcher/.env.content-publishing-service delete mode 100644 services/content-watcher/.env.dev delete mode 100644 services/content-watcher/docs/.gitkeep create mode 100644 services/content-watcher/docs/content_watcher_service_arch.drawio create mode 100644 services/content-watcher/docs/content_watcher_service_arch.drawio.png create mode 100644 services/content-watcher/jest-setup.ts create mode 100644 services/content-watcher/scripts/webhook-cat.cjs diff --git a/services/content-watcher/.dockerignore b/services/content-watcher/.dockerignore index b1f29d38..44c725c7 100644 --- a/services/content-watcher/.dockerignore +++ b/services/content-watcher/.dockerignore @@ -1,7 +1,35 @@ -Dockerfile -.dockerignore -node_modules -npm-debug.log -dist -.env* +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/.next +**/.cache +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/charts +**/docker-compose* +**/compose.y*ml +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +**/build +**/dist +LICENSE +README.md env.template diff --git a/services/content-watcher/.env.content-publishing-service b/services/content-watcher/.env.content-publishing-service new file mode 100644 index 00000000..281556db --- /dev/null +++ b/services/content-watcher/.env.content-publishing-service @@ -0,0 +1,12 @@ +CHAIN_ENVIRONMENT=dev +PROVIDER_ID=1 +PROVIDER_ACCOUNT_SEED_PHRASE=//Alice +FILE_UPLOAD_MAX_SIZE_IN_BYTES=10000000 # ~10Mb +ASSET_EXPIRATION_INTERVAL_SECONDS=300 +BATCH_INTERVAL_SECONDS=12 +BATCH_MAX_COUNT=1000 +ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 +CAPACITY_LIMIT={"type":"percentage", "value":80} +REDIS_URL=redis://redis:6379 +FREQUENCY_URL=ws://frequency:9944 +IPFS_ENDPOINT=http://ipfs:5001 diff --git a/services/content-watcher/.env.dev b/services/content-watcher/.env.dev deleted file mode 100644 index 08d2d2c9..00000000 --- a/services/content-watcher/.env.dev +++ /dev/null @@ -1,19 +0,0 @@ -# Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development -# IPFS_ENDPOINT="https://ipfs.infura.io:5001" -# IPFS_BASIC_AUTH_USER="Infura Project ID Here or Blank for Kubo RPC" -# IPFS_BASIC_AUTH_SECRET="Infura Secret Here or Blank for Kubo RPC" -# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" -IPFS_ENDPOINT="http://127.0.0.1:5001" -IPFS_BASIC_AUTH_USER="" -IPFS_BASIC_AUTH_SECRET="" -IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" - -FREQUENCY_URL=ws://127.0.0.1:9944 -STARTING_BLOCK=1 -REDIS_URL=redis://127.0.0.1:6379 -BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 -QUEUE_HIGH_WATER=1000 -WEBHOOK_FAILURE_THRESHOLD=4 -WEBHOOK_RETRY_INTERVAL_SECONDS=10 -ENVIRONMENT="dev" -API_PORT=3000 diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.docker.dev index cfdeaa37..953de96f 100644 --- a/services/content-watcher/.env.docker.dev +++ b/services/content-watcher/.env.docker.dev @@ -1,23 +1,42 @@ -IPFS_ENDPOINT=http://kubo_ipfs:5001 -IPFS_BASIC_AUTH_USER= -IPFS_BASIC_AUTH_SECRET= -IPFS_GATEWAY_URL=http://kubo_ipfs:8080/ipfs/[CID] +# Tweak values for local development +# Content Publishing Service in Docker Compose will override these values with `.env.content-publishing-service` -CHAIN_ENVIRONMENT=dev +# URL to IPFS endpoint +# IPFS_ENDPOINT="https://ipfs.infura.io:5001" +IPFS_ENDPOINT="http://ipfs:5001" + +# If using Infura with auth required for read access, put Project ID here, or leave blank for Kubo RPC +# IPFS_BASIC_AUTH_USER= + +# If using Infura with auth required for read access, put auth token here, or leave blank for Kubo RPC +# IPFS_BASIC_AUTH_SECRET= + +# IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID +# IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" +IPFS_GATEWAY_URL="http://ipfs:8080/ipfs/[CID]" + +# Blockchain node address FREQUENCY_URL=ws://frequency:9944 -PROVIDER_ID=1 + +# Block number from which the service will start scanning the chain +STARTING_BLOCK=1 + +# Redis URL REDIS_URL=redis://redis:6379 + +# How many minutes to delay between successive scans of the chain +# for new accounts (after end of chain is reached) BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 + +# Max number of jobs allowed on the queue before +# blockchain scan will be paused to allow queue to drain QUEUE_HIGH_WATER=1000 -PROVIDER_ACCOUNT_SEED_PHRASE="//Alice" -WEBHOOK_FAILURE_THRESHOLD=3 + +# Number of retry attempts if a registered webhook call fails +WEBHOOK_FAILURE_THRESHOLD=4 + +# Number of seconds between webhook retry attempts when failing WEBHOOK_RETRY_INTERVAL_SECONDS=10 -CAPACITY_LIMIT='{"type":"percentage", "value":80}' -ENVIRONMENT=dev -API_PORT=3000 -FILE_UPLOAD_MAX_SIZE_IN_BYTES=2000000000 -ASSET_EXPIRATION_INTERVAL_SECONDS=300 -BATCH_INTERVAL_SECONDS=12 -BATCH_MAX_COUNT=1000 -ASSET_UPLOAD_VERIFICATION_DELAY_SECONDS=5 +# Port that the application REST endpoints listen on +API_PORT=3000 diff --git a/services/content-watcher/.gitignore b/services/content-watcher/.gitignore index f663a812..1f9dee7b 100644 --- a/services/content-watcher/.gitignore +++ b/services/content-watcher/.gitignore @@ -1,7 +1,10 @@ node_modules dist -.env +.env* +!.env.docker.dev +!.env.content-publishing-service .vscode coverage .idea docs/*.html +*.bkp diff --git a/services/content-watcher/Dockerfile b/services/content-watcher/Dockerfile index d519bb34..26fd1aa1 100644 --- a/services/content-watcher/Dockerfile +++ b/services/content-watcher/Dockerfile @@ -1,33 +1,72 @@ -# Use a multi-stage build for efficiency -FROM node:20 AS builder +# syntax=docker/dockerfile:1 +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/go/dockerfile-reference/ + +# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7 + +ARG NODE_VERSION=20 + +################################################################################ +# Use node image for base image for all stages. +FROM node:${NODE_VERSION}-alpine as base + +# Set working directory for all build stages. WORKDIR /app -COPY package*.json ./ -RUN npm ci +################################################################################ +# Create a stage for installing production dependecies. +FROM base as deps -COPY . . +# Download dependencies as a separate step to take advantage of Docker's caching. +# Leverage a cache mount to /root/.npm to speed up subsequent builds. +# Leverage bind mounts to package.json and package-lock.json to avoid having to copy them +# into this layer. +RUN --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=package-lock.json,target=package-lock.json \ + --mount=type=cache,target=/root/.npm \ + npm ci --omit=dev -# Build the application +################################################################################ +# Create a stage for building the application. +FROM deps as build + +# Download additional development dependencies before building, as some projects require +# "devDependencies" to be installed to build. If you don't need this, remove this step. +RUN --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=package-lock.json,target=package-lock.json \ + --mount=type=cache,target=/root/.npm \ + npm ci + +# Copy the rest of the source files into the image. +COPY . . +# Run the build script. RUN npm run build -# Production stage -FROM node:20 +################################################################################ +# Create a new stage to run the application with minimal runtime dependencies +# where the necessary files are copied from the build stage. +FROM base as final -WORKDIR /app +# Use production node environment by default. +ENV NODE_ENV production + +# Run the application as a non-root user. +USER node -COPY --from=builder /app/dist ./dist -COPY package*.json ./ +# Copy package.json so that package manager commands can be used. +COPY package.json . -RUN npm ci --omit=dev +# Copy the production dependencies from the deps stage and also +# the built application from the build stage into the image. +COPY --from=deps /app/node_modules ./node_modules +COPY --from=build /app/dist ./dist -# We want jq and curl in the final image, but we don't need the support files -RUN apt-get update && \ - apt-get install -y jq curl tini && \ - apt-get clean && \ - rm -rf /usr/share/doc /usr/share/man /usr/share/zsh +# Expose the port that the application listens on. EXPOSE 3000 -ENTRYPOINT ["/usr/bin/tini", "--", "npm", "run", "start:prod"] +# Run the application. +ENTRYPOINT ["/usr/bin/tini", "--", "node", "dist/apps/api/main.js"] diff --git a/services/content-watcher/ENVIRONMENT.md b/services/content-watcher/ENVIRONMENT.md index df3811cd..55c3fa13 100644 --- a/services/content-watcher/ENVIRONMENT.md +++ b/services/content-watcher/ENVIRONMENT.md @@ -2,17 +2,17 @@ This application recognizes the following environment variables: -| Name | Description | Range/Type | Required? | Default | -| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------: | :----------: | :-----: | -|`API_PORT` | HTTP port that the application listens on | 1025 - 65535 | | 3000 | -|`BLOCKCHAIN_SCAN_INTERVAL_MINUTES` | How many minutes to delay between successive scans of the chain for new accounts (after end of chain is reached) | > 0 | | 180 | -|`FREQUENCY_URL` | Blockchain node address | http(s): or ws(s): URL | Y | | -|`IPFS_BASIC_AUTH_SECRET`|If using Infura, put auth token here, or leave blank for Kubo RPC|string|N|blank| -|`IPFS_BASIC_AUTH_USER`|If using Infura, put Project ID here, or leave blank for Kubo RPC|string|N|blank| -|`IPFS_ENDPOINT`|URL to IPFS endpoint|URL|Y|| -|`IPFS_GATEWAY_URL`|IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID|URL template|Y|| -|`QUEUE_HIGH_WATER` | Max number of jobs allowed on the 'graphUpdateQueue' before blockchain scan will be paused to allow queue to drain | >= 100 | | 1000 | -|`REDIS_URL` | Connection URL for Redis | URL | Y | -|`STARTING_BLOCK`|Block number from which the service will start scanning the chain|> 0||1| -|`WEBHOOK_FAILURE_THRESHOLD` | Number of failures allowing in the provider webhook before the service is marked down | > 0 | | 3 | -|`WEBHOOK_RETRY_INTERVAL_SECONDS` | Number of seconds between provider webhook retry attempts when failing | > 0 | | 10 | +| Name | Description | Range/Type | Required? | Default | +| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ | :--------------------: | :-------: | :-----: | +| `API_PORT` | HTTP port that the application listens on | 1025 - 65535 | | 3000 | +| `BLOCKCHAIN_SCAN_INTERVAL_MINUTES` | How many minutes to delay between successive scans of the chain for new accounts (after end of chain is reached) | > 0 | | 180 | +| `FREQUENCY_URL` | Blockchain node address | http(s): or ws(s): URL | Y | | +| `IPFS_BASIC_AUTH_SECRET` | If required for read requests, put Infura auth token here, or leave blank for default Kubo RPC | string | N | blank | +| `IPFS_BASIC_AUTH_USER` | If required for read requests, put Infura Project ID here, or leave blank for default Kubo RPC | string | N | blank | +| `IPFS_ENDPOINT` | URL to IPFS endpoint | URL | Y | | +| `IPFS_GATEWAY_URL` | IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID | URL template | Y | | +| `QUEUE_HIGH_WATER` | Max number of jobs allowed on the '' before blockchain scan will be paused to allow queue to drain | >= 100 | | 1000 | +| `REDIS_URL` | Connection URL for Redis | URL | Y | +| `STARTING_BLOCK` | Block number from which the service will start scanning the chain | > 0 | | 1 | +| `WEBHOOK_FAILURE_THRESHOLD` | Number of failures allowing in the provider webhook before the service is marked down | > 0 | | 3 | +| `WEBHOOK_RETRY_INTERVAL_SECONDS` | Number of seconds between provider webhook retry attempts when failing | > 0 | | 10 | diff --git a/services/content-watcher/INSTALLING.md b/services/content-watcher/INSTALLING.md index 0426f3f7..dba6620f 100644 --- a/services/content-watcher/INSTALLING.md +++ b/services/content-watcher/INSTALLING.md @@ -62,4 +62,4 @@ The following is a list of environment variables that may be set to control the |`STARTING_BLOCK`|**maybe**|Starting block for scanner to scan from|_none_| |`REDIS_URL`|**yes**|URL used to connect to Redis instance|_none_
\*preset to the internal Redis URL in the standalone container| |`BLOCKCHAIN_SCAN_INTERVAL_MINUTES`|no|# of minutes to wait in between scans of the blockchain|180| -|`QUEUE_HIGH_WATER`|no|# of pending graph scan queue entries to allow before pausing blockchain scanning until the next scan cycle|1000| +|`QUEUE_HIGH_WATER`|no|# of pending queue entries to allow before pausing blockchain scanning until the next scan cycle|1000| diff --git a/services/content-watcher/README.md b/services/content-watcher/README.md index 9242a137..e31a9a54 100644 --- a/services/content-watcher/README.md +++ b/services/content-watcher/README.md @@ -1,75 +1,347 @@ # Content Watcher -Content Watcher is a service that watches for events on Frequency and produces DSNP content to respective output channels. + -## Table of Contents +# 📗 Table of Contents -- [Content Watcher](#content-watcher) - - [Table of Contents](#table-of-contents) +- [📖 About the Project](#about-project) +- [🔍 Arch Map](#-arch-maps) +- [🛠 Built With](#-built-with) + - [Tech Stack](#tech-stack) + - [Key Features](#key-features) +- [🚀 Live OpenAPI Docs](#-live-docs) +- [💻 Getting Started](#-getting-started) - [Prerequisites](#prerequisites) - - [Getting Started](#getting-started) - - [Clone the Repository](#clone-the-repository) - - [Run a Full End-to-End Test](#run-a-full-end-to-end-test) + - [Environment Variables](#environment-variables) + - [Setup](#setup) + - [Install](#install) + - [Usage](#usage) + - [Swagger](#swagger-ui) + - [Queue Management](#queue-management) + - [Run tests](#run-tests) + - [Linting](#linting) + - [Formatting](#auto-format) +- [🤝 Contributing](#-contributing) +- [⭐️ Show your support](#-support) +- [🙏 Acknowledgements](#-acknowledgements) +- [❓FAQ](#faq) +- [📝 License](#-license) -## Prerequisites + -Before you begin, ensure you have met the following requirements: +# 📖 Content Watcher Service -- **Docker:** Content Watcher is designed to run in a Docker environment. Make sure Docker is installed on your system. +The Content Watcher Service is part of the "Social Gateway" collection of services that provides a familiar callback API to retrieve content and publishing announcements from the Frequency chain. The service handles all of the necessary blockchain interaction and allows clients to interact using a familiar, web2-friendly interface. -## Getting Started + -Follow these steps to set up and run Content Watcher: +## 🔭 Arch Maps -### 1. Clone the Repository -Clone the Content Watcher repository to your local machine: -```bash -git clone https://github.com/amplicalabls/content-watcher-service.git +The Content Watcher Service provides a webhook system for receiving notice of new content. +Even with a crawl request, the results are not retreived, but pushed to the webhook. + +![Content Watcher Service Arch](./docs/content_watcher_service_arch.drawio.png) + +

(back to top)

+ +## 🛠 Built With + +### Tech Stack + +
+ Server + +
+ +
+ Data store + +
+ +
+ Frameworks and Libraries + +
+ +
+ Polkadot and DSNP Integration + +
+ +
+ Testing + +
+ +
+ Formatting + +
+ +
+ Build and Deployment + +
+ + + +### Key Features + +#### API +- **Parse DSNP Messages on Frequency** +- **Send content to registered webhooks** +- **Simple Schema and MSA Id based filtering** + +#### Scanner API +- **Start, Stop, and restart scanning** + +

(back to top)

+ + + +## 🚀 Live Docs + +- [Live Docs](https://amplicalabs.github.io/content-watcher-service/) +- [API Documentation](https://amplicalabs.github.io/content-watcher-service/) +- [GitHub](https://github.com/AmplicaLabs/content-watcher-service) + +

(back to top)

+ + + +## 💻 Getting Started + +This guide is tailored for developers working in the code base for the Content Watcher Service itself. For a more tutorial tailored more for developers wanting to deploy the Content Watcher Service as part of the broader Social Gateway in order to develop their own Social Gateway app, visit [Live Docs](https://amplicalabs.github.io/gateway/). + +To prepare and run a local instance of the Content Watcher Service for local development, follow the guide below. + +### Prerequisites + +In order to run this project you need: + +- [Nodejs](https://nodejs.org) +- [Docker](https://www.docker.com) or Docker-compatible container system for running Gateway Services + - (note, Docker is not strictly required; all of the services described below may be installed or built & run locally, but that is outside the scope of this guide) + +### Environment Variables + +Use the provided [env.template](./env.template) file to create an initial environment for the application, and edit as desired. Additional documentation on the complete set of environment variables is provided in the [ENVIRONMENT.md](./ENVIRONMENT.md) file. + + 1. For running locally, copy to the template file to `.env` and update as needed. + + ```sh + cp env.template .env + ``` + + 2. Configure the environment variable values according to your environment. + - Docker: `.env.docker.dev` + - Local: `.env` + +### Setup + +Clone this repository to your desired folder: + +Example commands: + +```sh + git clone git@github.com:AmplicaLabs/content-watcher-service.git + cd content-watcher-service ``` -### 2. Configure the app -The application is configurable by means of the environment. Default environment files are provided with sane values. The available environment configuration variables are document [here](./ENVIRONMENT.md), and a sample file is located [here](./env.template) +### Install -Since running a full stack supporting the content-watcher service necessitates having an instance of the content-publishing services, those services are included in the docker-compose profile. The content-publishing services are separately configured using [.env.cp.docker.dev](./.env.cp.docker.dev). Documentation for that configuration can be found [here](https://github.com/AmplicaLabs/content-publishing-service/blob/main/ENVIRONMENT.md) +Install NPM Dependencies: -### 3. Start the service: -Run the following command to start the service: -```bash -docker-compose up -d +```sh + npm install ``` -## Testing -### Run a Full End-to-End Test -1. Execute the following `make` command to deploy the entire stack: - ```bash - make test-start-services - ``` +### Usage + +To run the project, execute the following command: + +#### 1. Start the required auxiliary services + + Frequency node, Redis, IPFS + + ```sh + docker compose up -d frequency redis ipfs + ``` + +#### 2. [Optional] Start the publishing services + + Content Publishing Service + + ```sh + docker compose up -d content-publishing-service-api content-publishing-service-worker + ``` + +#### 3. Start the application services + + Each of the application services may be run either under Docker or bare-metal, depending on your preferred development workflow. + + #### Running locally + ```sh + npm run start:api:dev + ``` + + -- or -- - This command will set up the following services: - - **Frequency:** A local instance of Frequency will be set up with the default instant sealing mode. - - **Redis:** A local instance of Redis will be initiated and configured for use by content publishing and content watcher services. - - **Kubo IPFS:** A local instance of IPFS will be initiated and configured for use in content publishing and retrieval. - - **Content Publishing API:** A local instance of the content publishing API will be utilized to publish content to IPFS and Frequency for content watcher tests. - - **Content Publishing Worker:** A local instance of the content publishing worker will be employed to publish content to IPFS and Frequency for content watcher tests via dedicated processors. + #### Running under Docker + ```sh + docker compose up [-d] content-watcher-service + ``` - The following setup scenarios will be executed during the stack initialization: +#### 4. [Optional] Setup a basic publisher - - **Chain Setup Scenario:** A provider with MSA=1 will be created, with some user accounts, along with delegation to the provider. Capacity will be staked to MSA=1 to enable the provider to publish content on behalf of users. - - **DSNP Schemas:** DSNP schemas will be registered on Frequency. - - **Publish Some Example Content:** Example content will be published to IPFS and Frequency. Check the progress of content publishing at [Content Publishing BullBoard](http://0.0.0.0:3001/queues). + Setup provider and users for the publishing service. + + ```sh + npm run local:init + ``` + +#### 5. [Optional] Webhook registration + + Start a simple webhook that will just echo out responses to the console. + + ```sh + npm run local:webhook + ``` + +#### 5. [Optional] Trigger Content + + Publishes some random content through the Content Publishing Service in Docker. + Can be run more than once. + + ```sh + npm run local:publish + ``` + +#### 5. Check the job in BullUI, to monitor job progress based on defined tests. + +### Swagger UI +Check out the Swagger UI hosted on the app instance at http://localhost:3000/api/docs/swagger to view the API documentation and submit requests to the service. + +### Queue Management +You may also view and manage the application's queue at http://localhost:3000/queues. + +For the Content Publishing Service Queue go to http://localhost:3001/queues. + +### Run unit tests + +To run unit tests, run the following command: + +```sh + npm test +``` + +### Run e2e tests: + +1. Execute the following `make` command to deploy the entire stack: + ```bash + make test-services-start + ``` 2. Run the following `make` command to execute the content watcher tests: ```bash make test-e2e ``` -3. Alternatively, create a `.env` file, run `nest start api` to start the content watcher as a standalone service, register a webhook with the content watcher using [swagger](http://0.0.0.0:3000/api/docs/swagger#), and try the following scenarios: +3. Alternatively, create a `.env` file, run `npm run start:api` to start the content watcher as a standalone service, register a webhook with the content watcher using [swagger](http://0.0.0.0:3000/api/docs/swagger#), and try the following scenarios: - **Reset Scanner:** This action will reset the scanner to start from the beginning of the chain or whichever block is chosen to start with. Upon successful parsing, a respective announcement will be made to the webhook. - **Put a Search Request:** This action will put a search request on the queue. The request requires a start block and end block. Upon successful parsing, a respective announcement will be made to the webhook. -## Swagger UI -Check out the Swagger UI hosted on the app instance at [\/api/docs/swagger](http://localhost:3000/api/docs/swagger) to view the API documentation and submit requests to the service. +### Linting: + +```sh + npm run lint +``` + +### Auto-format: + +```sh + npm run format +``` + +

(back to top)

+ + + +## 🤝 Contributing + +Contributions, issues, and feature requests are welcome! + +- [Contributing Guidelines](https://github.com/AmplicaLabs/gateway/blob/main/CONTRIBUTING.md) +- [Open Issues](https://github.com/AmplicaLabs/content-watcher-service/issues) + +

(back to top)

+ + + +## ⭐️ Show your support + +If you would like to explore contributing bug fixes or enhancements, issues with the label `good-first-issue` can be a good place to start. + +

(back to top)

+ + + +## 🙏 Acknowledgements + +Thank you to [Frequency](https://www.frequency.xyz) for assistance and documentation making this possible. + + + +## ❓FAQ + +- **Can I use this service in my production social app?** + + - Yes. All the Gateway Services are intended to be ready-to-use out of the box as part of the fabric of your own social media app using DSNP on Frequency. + +- **Does this service index content?** + + - No. This can be used by your own indexing service to get access to the content, but the service is intentially limited to getting the content and further customization is open to you. + +- **Does this service filter content?** + + - No. This can be used by your own content filtering service to get new content and then have your custom service process them. + +

(back to top)

+ + + +## 📝 License + +This project is [Apache 2.0](./LICENSE) licensed. -## Queue Management -You may also view and manage the application's queue at [\/queues](http://localhost:3000/queues). +

(back to top)

diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 3395d7c1..ab248be5 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -3,7 +3,7 @@ import { InjectRedis } from '@songkeys/nestjs-redis'; import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; -import { ContentSearchRequestDto, REQUEST_QUEUE_NAME, ResetScannerDto, calculateJobId } from '../../../libs/common/src'; +import { ContentSearchRequestDto, REQUEST_QUEUE_NAME, calculateJobId } from '../../../libs/common/src'; import { ScannerService } from '../../../libs/common/src/scanner/scanner'; import { EVENTS_TO_WATCH_KEY, REGISTERED_WEBHOOK_KEY } from '../../../libs/common/src/constants'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; diff --git a/services/content-watcher/apps/api/test/app.e2e-spec.ts b/services/content-watcher/apps/api/test/app.e2e-spec.ts index 45b983ca..08b5c39e 100644 --- a/services/content-watcher/apps/api/test/app.e2e-spec.ts +++ b/services/content-watcher/apps/api/test/app.e2e-spec.ts @@ -1,66 +1,47 @@ -import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; import request from 'supertest'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { ApiModule } from '../src/api.module'; -import { BlockchainService } from '../../../libs/common/src/blockchain/blockchain.service'; import { ResetScannerDto } from '../../../libs/common/src'; -describe('Content Watcher E2E request verification!', () => { - let app: INestApplication; - let module: TestingModule; - // eslint-disable-next-line no-promise-executor-return - const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); - - beforeEach(async () => { - module = await Test.createTestingModule({ - imports: [ApiModule], - }).compile(); - - app = module.createNestApplication(); - const eventEmitter = app.get(EventEmitter2); - eventEmitter.on('shutdown', async () => { - await app.close(); - }); - app.useGlobalPipes(new ValidationPipe()); - app.enableShutdownHooks(); - await app.init(); - const blockchainService = app.get(BlockchainService); - await blockchainService.isReady(); +const WATCHER_URI = 'http://localhost:3000'; +describe('Content Watcher E2E request verification!', () => { + const webhookUrl = 'http://localhost:3005/api/webhook'; + it('(PUT) /api/registerWebhook', async () => { // register webhook '(Put) /api/registerWebhook' const webhookRegistrationDto = { - url: 'http://localhost:3005/api/webhook', + url: webhookUrl, announcementTypes: ['Broadcast', 'Reaction', 'Tombstone', 'Reply', 'Update'], }; - const response = await request(app.getHttpServer()).put('/api/registerWebhook').send(webhookRegistrationDto).expect(200); - }); + await request(WATCHER_URI).put('/api/registerWebhook').send(webhookRegistrationDto).expect(200); + }, 15000); - it('(GET) /api/health', () => request(app.getHttpServer()).get('/api/health').expect(200).expect({ status: 200 })); + it('(GET) /api/getRegisteredWebhooks', async () => { + // Verify that the webhook was registered + const response = await request(WATCHER_URI).get('/api/getRegisteredWebhooks').send().expect(200); + + expect(response.body).toHaveProperty('registeredWebhooks'); + expect(response.body.registeredWebhooks).toHaveLength(5); + response.body.registeredWebhooks.forEach((registered) => { + expect(registered.urls).toContain(webhookUrl); + }); + }, 15000); + + it('(GET) /api/health', () => request(WATCHER_URI).get('/api/health').expect(200).expect({ status: 200 })); it('(Post) /api/resetScanner', async () => { const resetScannerDto: ResetScannerDto = { - blockNumber: '0', + blockNumber: 1, }; - const response = await request(app.getHttpServer()).post('/api/resetScanner').send(resetScannerDto).expect(201); + await request(WATCHER_URI).post('/api/resetScanner').send(resetScannerDto).expect(201); }, 15000); it('(Put) /api/search - search for content', async () => { const searchRequest = { - startBlock: '0', - endBlock: '100', + startBlock: 1, + endBlock: 100, }; - const response = await request(app.getHttpServer()).put('/api/search').send(searchRequest).expect(200); + const response = await request(WATCHER_URI).put('/api/search').send(searchRequest).expect(200); expect(response.body).toHaveProperty('jobId'); const { jobId } = response.body; expect(jobId).not.toBeNull(); }, 15000); - - afterEach(async () => { - try { - await app.close(); - } catch (err) { - console.error(err); - } - }, 15000); }); diff --git a/services/content-watcher/dev.Dockerfile b/services/content-watcher/dev.Dockerfile index 9de164f4..9cfdcebe 100644 --- a/services/content-watcher/dev.Dockerfile +++ b/services/content-watcher/dev.Dockerfile @@ -1,7 +1,22 @@ -FROM node:20 +# syntax=docker/dockerfile:1 + +# Comments are provided throughout this file to help you get started. +# If you need more help, visit the Dockerfile reference guide at +# https://docs.docker.com/go/dockerfile-reference/ + +# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7 + +ARG NODE_VERSION=20 + +FROM node:${NODE_VERSION}-alpine WORKDIR /app +# Run the application as a non-root user. +USER node + +# Expose the port that the application listens on. EXPOSE 3000 -ENTRYPOINT [ "npm", "run", "start:watch" ] +# Run the application. +ENTRYPOINT [ "npm", "run", "start:api:watch" ] diff --git a/services/content-watcher/docker-compose.yaml b/services/content-watcher/docker-compose.yaml index 00a72725..b3f4b6a0 100644 --- a/services/content-watcher/docker-compose.yaml +++ b/services/content-watcher/docker-compose.yaml @@ -29,7 +29,7 @@ services: volumes: - chainstorage:/data - kubo_ipfs: + ipfs: image: ipfs/kubo:latest ports: - 4001:4001 @@ -48,12 +48,13 @@ services: - 3001:3000 env_file: - .env.docker.dev + - .env.content-publishing-service environment: - START_PROCESS=api depends_on: - redis - frequency - - kubo_ipfs + - ipfs networks: - content-watcher-service @@ -63,12 +64,13 @@ services: platform: linux/amd64 env_file: - .env.docker.dev + - .env.content-publishing-service environment: - START_PROCESS=worker depends_on: - redis - frequency - - kubo_ipfs + - ipfs networks: - content-watcher-service @@ -82,12 +84,14 @@ services: - content-watcher-service:latest env_file: - .env.docker.dev + ports: + - 3000:3000 volumes: - ./:/app depends_on: - redis - frequency - - kubo_ipfs + - ipfs - content-publishing-service-api - content-publishing-service-worker networks: diff --git a/services/content-watcher/docs/.gitkeep b/services/content-watcher/docs/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/services/content-watcher/docs/content_watcher_service_arch.drawio b/services/content-watcher/docs/content_watcher_service_arch.drawio new file mode 100644 index 00000000..e9f0a0fd --- /dev/null +++ b/services/content-watcher/docs/content_watcher_service_arch.drawio @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/services/content-watcher/docs/content_watcher_service_arch.drawio.png b/services/content-watcher/docs/content_watcher_service_arch.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..6bcf9d40eece0c2ef67cf9045d2fa824ccd22442 GIT binary patch literal 286970 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

bI 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

` patch The single controller has been split into separate controllers for functional groups. `swagger.yaml` removed in favor of the auto-generated `swagger.json` --- .../apps/api/src/api.controller.ts | 112 ----- .../apps/api/src/api.module.ts | 20 +- .../apps/api/src/api.service.ts | 5 + .../api/src/controllers/health.controller.ts | 44 ++ .../apps/api/src/controllers/index.ts | 2 + .../apps/api/src/controllers/v1/index.ts | 3 + .../src/controllers/v1/scanner.controller.ts | 65 +++ .../src/controllers/v1/search.controller.ts | 31 ++ .../src/controllers/v1/webhook.controller.ts | 53 +++ services/content-watcher/apps/api/src/main.ts | 2 +- .../content-watcher/apps/api/src/metadata.ts | 2 +- .../apps/api/tsconfig.app.json | 19 +- .../libs/common/src/config/swagger_config.ts | 2 +- services/content-watcher/package.json | 25 +- services/content-watcher/swagger.json | 396 ++++++++++++++++++ services/content-watcher/swagger.yaml | 194 --------- services/content-watcher/tsconfig.json | 15 +- 17 files changed, 657 insertions(+), 333 deletions(-) delete mode 100644 services/content-watcher/apps/api/src/api.controller.ts create mode 100644 services/content-watcher/apps/api/src/controllers/health.controller.ts create mode 100644 services/content-watcher/apps/api/src/controllers/index.ts create mode 100644 services/content-watcher/apps/api/src/controllers/v1/index.ts create mode 100644 services/content-watcher/apps/api/src/controllers/v1/scanner.controller.ts create mode 100644 services/content-watcher/apps/api/src/controllers/v1/search.controller.ts create mode 100644 services/content-watcher/apps/api/src/controllers/v1/webhook.controller.ts create mode 100644 services/content-watcher/swagger.json delete mode 100644 services/content-watcher/swagger.yaml diff --git a/services/content-watcher/apps/api/src/api.controller.ts b/services/content-watcher/apps/api/src/api.controller.ts deleted file mode 100644 index 630c4a36..00000000 --- a/services/content-watcher/apps/api/src/api.controller.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Body, Controller, Delete, Get, HttpStatus, Logger, Post, Put, Query } from '@nestjs/common'; -import { ApiBody, ApiOkResponse, ApiQuery } from '@nestjs/swagger'; -import { ApiService } from './api.service'; -import { ResetScannerDto, ContentSearchRequestDto } from '../../../libs/common/src'; -import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; -import { WebhookRegistrationDto } from '../../../libs/common/src/dtos/subscription.webhook.dto'; - -@Controller('api') -export class ApiController { - private readonly logger: Logger; - - constructor(private apiService: ApiService) { - this.logger = new Logger(this.constructor.name); - } - - // eslint-disable-next-line class-methods-use-this - @Get('health') - health() { - return { - status: HttpStatus.OK, - }; - } - - @Post('resetScanner') - @ApiBody({ - description: 'blockNumber', - type: ResetScannerDto, - }) - resetScanner(@Body() resetScannerDto: ResetScannerDto) { - return this.apiService.resetScanner(resetScannerDto); - } - - @Post('setWatchOptions') - @ApiBody({ - description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds', - type: ChainWatchOptionsDto, - }) - setWatchOptions(@Body() watchOptions: ChainWatchOptionsDto) { - return this.apiService.setWatchOptions(watchOptions); - } - - @Post('pauseScanner') - pauseScanner() { - return this.apiService.pauseScanner(); - } - - @Post('startScanner') - @ApiQuery({ - name: 'immediate', - description: 'immediate: whether to resume scan immediately (true), or wait until next scheduled scan (false)', - type: 'boolean', - required: false, - }) - startScanner(@Query('immediate') immediate?: boolean) { - this.logger.debug(`Resuming scan; immediate=${immediate}`); - return this.apiService.resumeScanner(immediate); - } - - @Put('search') - @ApiBody({ - description: 'Search for DSNP content by id, startBlock, endBlock, and filters', - type: ContentSearchRequestDto, - }) - @ApiOkResponse({ - description: 'Returns a jobId to be used to retrieve the results', - type: String, - }) - async search(@Body() searchRequest: ContentSearchRequestDto) { - const jobResult = await this.apiService.searchContent(searchRequest); - - return { - status: HttpStatus.OK, - jobId: jobResult.id, - }; - } - - @Put('registerWebhook') - @ApiBody({ - description: 'Register a webhook to be called when a new content is created', - type: WebhookRegistrationDto, - }) - async registerWebhook(@Body() webhookRegistrationDto: WebhookRegistrationDto) { - this.logger.debug(`Registering webhook ${JSON.stringify(webhookRegistrationDto)}`); - await this.apiService.setWebhook(webhookRegistrationDto); - return { - status: HttpStatus.OK, - }; - } - - @Delete('clearAllWebHooks') - async clearAllWebHooks() { - this.logger.debug('Unregistering webhooks'); - await this.apiService.clearAllWebhooks(); - return { - status: HttpStatus.OK, - }; - } - - @Get('getRegisteredWebhooks') - @ApiOkResponse({ - description: 'Returns a list of registered webhooks', - type: [WebhookRegistrationDto], - }) - async getRegisteredWebhooks() { - this.logger.debug('Getting registered webhooks'); - const registeredWebhooks = await this.apiService.getRegisteredWebhooks(); - return { - status: HttpStatus.OK, - registeredWebhooks, - }; - } -} diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index aa6addc7..57f41d6d 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -6,16 +6,16 @@ import { RedisModule } from '@songkeys/nestjs-redis'; import { BullBoardModule } from '@bull-board/nestjs'; import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; import { ExpressAdapter } from '@bull-board/express'; -import { ApiController } from './api.controller'; -import * as QueueConstants from '../../../libs/common/src'; import { ApiService } from './api.service'; -import { ConfigModule } from '../../../libs/common/src/config/config.module'; -import { ConfigService } from '../../../libs/common/src/config/config.service'; -import { ScannerModule } from '../../../libs/common/src/scanner/scanner.module'; -import { BlockchainModule } from '../../../libs/common/src/blockchain/blockchain.module'; -import { CrawlerModule } from '../../../libs/common/src/crawler/crawler.module'; -import { IPFSProcessorModule } from '../../../libs/common/src/ipfs/ipfs.module'; -import { PubSubModule } from '../../../libs/common/src/pubsub/pubsub.module'; +import { HealthController, ScanControllerV1, SearchControllerV1, WebhookControllerV1 } from './controllers'; +import { BlockchainModule } from '@libs/common/blockchain/blockchain.module'; +import { CrawlerModule } from '@libs/common/crawler/crawler.module'; +import { IPFSProcessorModule } from '@libs/common/ipfs/ipfs.module'; +import { PubSubModule } from '@libs/common/pubsub/pubsub.module'; +import { ScannerModule } from '@libs/common/scanner/scanner.module'; +import { ConfigModule } from '@libs/common/config/config.module' +import { ConfigService } from '@libs/common/config/config.service' +import * as QueueConstants from '@libs/common'; @Module({ imports: [ @@ -159,7 +159,7 @@ import { PubSubModule } from '../../../libs/common/src/pubsub/pubsub.module'; ScheduleModule.forRoot(), ], providers: [ApiService], - controllers: [ApiController], + controllers: [HealthController, ScanControllerV1, SearchControllerV1, WebhookControllerV1], exports: [], }) export class ApiModule {} diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index ab248be5..3e506792 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -23,6 +23,11 @@ export class ApiService { this.logger = new Logger(this.constructor.name); } + public async getWatchOptions(): Promise { + const options = await this.redis.get(EVENTS_TO_WATCH_KEY); + return options ? JSON.parse(options) as ChainWatchOptionsDto : null; + } + public async setWatchOptions(watchOptions: ChainWatchOptionsDto) { this.logger.warn(`Setting watch options to ${JSON.stringify(watchOptions)}`); const currentWatchOptions = await this.redis.get(EVENTS_TO_WATCH_KEY); diff --git a/services/content-watcher/apps/api/src/controllers/health.controller.ts b/services/content-watcher/apps/api/src/controllers/health.controller.ts new file mode 100644 index 00000000..b15d3aa9 --- /dev/null +++ b/services/content-watcher/apps/api/src/controllers/health.controller.ts @@ -0,0 +1,44 @@ +/* eslint-disable class-methods-use-this */ +import { Controller, Get, HttpCode, HttpStatus } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +@Controller() +@ApiTags('health') +export class HealthController { + + // Health endpoint + @Get('healthz') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Check the health status of the service' }) + @ApiOkResponse({ description: 'Service is healthy' }) + healthz() { + return { + status: HttpStatus.OK, + message: 'Service is healthy', + }; + } + + // Live endpoint + @Get('livez') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Check the live status of the service' }) + @ApiOkResponse({ description: 'Service is live' }) + livez() { + return { + status: HttpStatus.OK, + message: 'Service is live', + }; + } + + // Ready endpoint + @Get('readyz') + @HttpCode(HttpStatus.OK) + @ApiOperation({ summary: 'Check the ready status of the service' }) + @ApiOkResponse({ description: 'Service is ready' }) + readyz() { + return { + status: HttpStatus.OK, + message: 'Service is ready', + }; + } +} diff --git a/services/content-watcher/apps/api/src/controllers/index.ts b/services/content-watcher/apps/api/src/controllers/index.ts new file mode 100644 index 00000000..16333bae --- /dev/null +++ b/services/content-watcher/apps/api/src/controllers/index.ts @@ -0,0 +1,2 @@ +export * from './health.controller'; +export * from './v1'; diff --git a/services/content-watcher/apps/api/src/controllers/v1/index.ts b/services/content-watcher/apps/api/src/controllers/v1/index.ts new file mode 100644 index 00000000..6819650a --- /dev/null +++ b/services/content-watcher/apps/api/src/controllers/v1/index.ts @@ -0,0 +1,3 @@ +export * from './scanner.controller'; +export * from './search.controller'; +export * from './webhook.controller'; diff --git a/services/content-watcher/apps/api/src/controllers/v1/scanner.controller.ts b/services/content-watcher/apps/api/src/controllers/v1/scanner.controller.ts new file mode 100644 index 00000000..b44daea5 --- /dev/null +++ b/services/content-watcher/apps/api/src/controllers/v1/scanner.controller.ts @@ -0,0 +1,65 @@ +import { Body, Controller, Get, HttpException, HttpStatus, Logger, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; +import { ApiService } from '#api/api.service' +import { ResetScannerDto } from '@libs/common'; +import { ChainWatchOptionsDto } from '@libs/common/dtos/chain.watch.dto'; + +@Controller('v1/scanner') +@ApiTags('scanner') +export class ScanControllerV1 { + private readonly logger: Logger; + + constructor(private apiService: ApiService) { + this.logger = new Logger(this.constructor.name); + } + + @Post('reset') + @ApiOperation({ summary: 'Reset blockchain scan to a specific block number or offset from the current position'}) + @ApiBody({ + description: 'blockNumber', + type: ResetScannerDto, + }) + resetScanner(@Body() resetScannerDto: ResetScannerDto) { + return this.apiService.resetScanner(resetScannerDto); + } + + @Get('options') + @ApiOperation({ summary: 'Get the current watch options for the blockchain content event scanner' }) + async getWatchOptions(): Promise { + const options = await this.apiService.getWatchOptions(); + if (!options) { + throw new HttpException('Not found', HttpStatus.NOT_FOUND); + } + + return options; + } + + @Post('options') + @ApiOperation({ summary: 'Set watch options to filter the blockchain content scanner by schemas or MSA IDs'}) + @ApiBody({ + description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds', + type: ChainWatchOptionsDto, + }) + setWatchOptions(@Body() watchOptions: ChainWatchOptionsDto) { + return this.apiService.setWatchOptions(watchOptions); + } + + @Post('pause') + @ApiOperation({ summary: 'Pause the blockchain scanner' }) + pauseScanner() { + return this.apiService.pauseScanner(); + } + + @Post('start') + @ApiOperation({ summary: 'Resume the blockchain content event scanner' }) + @ApiQuery({ + name: 'immediate', + description: 'immediate: whether to resume scan immediately (true), or wait until next scheduled scan (false)', + type: 'boolean', + required: false, + }) + startScanner(@Query('immediate') immediate?: boolean) { + this.logger.debug(`Resuming scan; immediate=${immediate}`); + return this.apiService.resumeScanner(immediate); + } +} diff --git a/services/content-watcher/apps/api/src/controllers/v1/search.controller.ts b/services/content-watcher/apps/api/src/controllers/v1/search.controller.ts new file mode 100644 index 00000000..8d0aa9ba --- /dev/null +++ b/services/content-watcher/apps/api/src/controllers/v1/search.controller.ts @@ -0,0 +1,31 @@ +/* eslint-disable class-methods-use-this */ +import { ApiService } from '#api/api.service'; +import { ContentSearchRequestDto } from '@libs/common'; +import { Body, Controller, HttpStatus, Post } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +@Controller('v1/search') +@ApiTags('search') +export class SearchControllerV1 { + + constructor(private readonly apiService: ApiService) {} + + @Post() + @ApiOperation({ summary: 'Search for DSNP content by id, start/end block, and filters' }) + @ApiBody({ + description: 'Search for DSNP content by id, startBlock, endBlock, and filters', + type: ContentSearchRequestDto, + }) + @ApiOkResponse({ + description: 'Returns a jobId to be used to retrieve the results', + type: String, + }) + async search(@Body() searchRequest: ContentSearchRequestDto) { + const jobResult = await this.apiService.searchContent(searchRequest); + + return { + status: HttpStatus.OK, + jobId: jobResult.id, + }; + } +} diff --git a/services/content-watcher/apps/api/src/controllers/v1/webhook.controller.ts b/services/content-watcher/apps/api/src/controllers/v1/webhook.controller.ts new file mode 100644 index 00000000..23db3837 --- /dev/null +++ b/services/content-watcher/apps/api/src/controllers/v1/webhook.controller.ts @@ -0,0 +1,53 @@ +import { ApiService } from '#api/api.service'; +import { WebhookRegistrationDto } from '@libs/common/dtos/subscription.webhook.dto'; +import { Body, Controller, Delete, Get, HttpStatus, Logger, Put } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +@Controller('v1/webhooks') +@ApiTags('webhooks') +export class WebhookControllerV1 { + private readonly logger: Logger; + + constructor(private apiService: ApiService) { + this.logger = new Logger(this.constructor.name); + } + + @Put() + @ApiOperation({ summary: 'Register a webhook to be called when new content is encountered on the chain' }) + @ApiBody({ + description: 'Register a webhook to be called when a new content is encountered', + type: WebhookRegistrationDto, + }) + async registerWebhook(@Body() webhookRegistrationDto: WebhookRegistrationDto) { + this.logger.debug(`Registering webhook ${JSON.stringify(webhookRegistrationDto)}`); + await this.apiService.setWebhook(webhookRegistrationDto); + return { + status: HttpStatus.OK, + }; + } + + @Delete() + @ApiOperation({ summary: 'Clear all previously registered webhooks' }) + async clearAllWebHooks() { + this.logger.debug('Unregistering webhooks'); + await this.apiService.clearAllWebhooks(); + return { + status: HttpStatus.OK, + }; + } + + @Get() + @ApiOperation({ summary: 'Get the list of currently registered webhooks' }) + @ApiOkResponse({ + description: 'Returns a list of registered webhooks', + type: [WebhookRegistrationDto], + }) + async getRegisteredWebhooks() { + this.logger.debug('Getting registered webhooks'); + const registeredWebhooks = await this.apiService.getRegisteredWebhooks(); + return { + status: HttpStatus.OK, + registeredWebhooks, + }; + } +} diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index c3dfd1d8..cc6d44fa 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -29,7 +29,7 @@ async function bootstrap() { try { app.enableShutdownHooks(); app.useGlobalPipes(new ValidationPipe()); - await initSwagger(app, '/api/docs/swagger'); + await initSwagger(app, '/docs/swagger'); logger.log(`Listening on port ${configService.apiPort}`); await app.listen(configService.apiPort); } catch (e) { diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index 7e2aac3e..c036530e 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -5,5 +5,5 @@ export default async () => { ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), ["../../../libs/common/src/dtos/chain.watch.dto"]: await import("../../../libs/common/src/dtos/chain.watch.dto") }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } }, "ResetScannerDto": { blockNumber: { required: true, type: () => Number } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/chain.watch.dto"), { "ChainWatchOptionsDto": { schemaIds: { required: true, type: () => [Number] }, dsnpIds: { required: true, type: () => [String] } } }], [import("../../../libs/common/src/dtos/request-job.dto"), { "ContentSearchRequestDto": { id: { required: true, type: () => String }, startBlock: { required: true, type: () => Number, minimum: 1 }, endBlock: { required: true, type: () => Number, minimum: 1 }, filters: { required: true, type: () => t["../../../libs/common/src/dtos/chain.watch.dto"].ChainWatchOptionsDto } } }], [import("../../../libs/common/src/dtos/subscription.webhook.dto"), { "WebhookRegistrationDto": { url: { required: true, type: () => String }, announcementTypes: { required: true, type: () => [String] } } }]], "controllers": [[import("./api.controller"), { "ApiController": { "health": {}, "resetScanner": { type: String }, "setWatchOptions": {}, "pauseScanner": {}, "startScanner": {}, "search": {}, "registerWebhook": {}, "clearAllWebHooks": {}, "getRegisteredWebhooks": {} } }]] } }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } }, "ResetScannerDto": { blockNumber: { required: false, type: () => Number, minimum: 1 }, rewindOffset: { required: false, type: () => Number }, immediate: { required: false, type: () => Boolean } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/chain.watch.dto"), { "ChainWatchOptionsDto": { schemaIds: { required: true, type: () => [Number] }, dsnpIds: { required: true, type: () => [String] } } }], [import("../../../libs/common/src/dtos/request-job.dto"), { "ContentSearchRequestDto": { id: { required: true, type: () => String }, startBlock: { required: true, type: () => Number, minimum: 1 }, endBlock: { required: true, type: () => Number, minimum: 1 }, filters: { required: true, type: () => t["../../../libs/common/src/dtos/chain.watch.dto"].ChainWatchOptionsDto } } }], [import("../../../libs/common/src/dtos/subscription.webhook.dto"), { "WebhookRegistrationDto": { url: { required: true, type: () => String }, announcementTypes: { required: true, type: () => [String] } } }]], "controllers": [[import("./controllers/health.controller"), { "HealthController": { "healthz": {}, "livez": {}, "readyz": {} } }], [import("./controllers/v1/scanner.controller"), { "ScanControllerV1": { "resetScanner": {}, "getWatchOptions": { type: t["../../../libs/common/src/dtos/chain.watch.dto"].ChainWatchOptionsDto }, "setWatchOptions": {}, "pauseScanner": {}, "startScanner": {} } }], [import("./controllers/v1/search.controller"), { "SearchControllerV1": { "search": {} } }], [import("./controllers/v1/webhook.controller"), { "WebhookControllerV1": { "registerWebhook": {}, "clearAllWebHooks": {}, "getRegisteredWebhooks": {} } }]] } }; }; \ No newline at end of file diff --git a/services/content-watcher/apps/api/tsconfig.app.json b/services/content-watcher/apps/api/tsconfig.app.json index e2e0b2ff..7d0bb74f 100644 --- a/services/content-watcher/apps/api/tsconfig.app.json +++ b/services/content-watcher/apps/api/tsconfig.app.json @@ -4,6 +4,21 @@ "declaration": false, "outDir": "../../dist/apps/api" }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] + "paths": { + "#app": [ + "src" + ], + "#app/*": [ + "src/*" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "test", + "**/*spec.ts" + ] } diff --git a/services/content-watcher/libs/common/src/config/swagger_config.ts b/services/content-watcher/libs/common/src/config/swagger_config.ts index 09fc1eec..330a6bc0 100644 --- a/services/content-watcher/libs/common/src/config/swagger_config.ts +++ b/services/content-watcher/libs/common/src/config/swagger_config.ts @@ -1,7 +1,7 @@ import { INestApplication } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import * as fs from 'fs'; -import metadata from '../../../../apps/api/src/metadata'; +import metadata from '#api/metadata'; export const initSwagger = async (app: INestApplication, apiPath: string) => { const options = new DocumentBuilder() diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index a5962a17..92197d13 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -11,7 +11,7 @@ "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", "build": "nest build", "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", - "generate-swagger-ui": "npx --yes @redocly/cli build-docs swagger.yaml --output=./docs/index.html", + "generate-swagger-ui": "npx --yes @redocly/cli build-docs swagger.json --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "docker-build": "docker build -t content-watcher-service .", "docker-build:dev": "docker-compose build", @@ -102,18 +102,31 @@ "typescript-eslint": "^7.9.0" }, "jest": { - "moduleFileExtensions": ["js", "json", "ts"], + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], "rootDir": ".", - "setupFiles": ["./jest-setup.ts"], + "setupFiles": [ + "./jest-setup.ts" + ], "testRegex": ".*\\.spec\\.ts$", - "testPathIgnorePatterns": [".*\\.mock\\.spec\\.ts$"], + "testPathIgnorePatterns": [ + ".*\\.mock\\.spec\\.ts$" + ], "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "collectCoverageFrom": ["**/*.(t|j)s"], + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], "coverageDirectory": "./coverage", "testEnvironment": "node", - "roots": ["/apps/", "/libs/"], + "roots": [ + "/apps/", + "/libs/" + ], "moduleNameMapper": { "^@content-watcher-common(|/.*)$": "/libs/common/src/$1" } diff --git a/services/content-watcher/swagger.json b/services/content-watcher/swagger.json new file mode 100644 index 00000000..2a684e05 --- /dev/null +++ b/services/content-watcher/swagger.json @@ -0,0 +1,396 @@ +{ + "openapi": "3.0.0", + "paths": { + "/healthz": { + "get": { + "operationId": "HealthController_healthz", + "summary": "Check the health status of the service", + "parameters": [], + "responses": { + "200": { + "description": "Service is healthy" + } + }, + "tags": [ + "health" + ] + } + }, + "/livez": { + "get": { + "operationId": "HealthController_livez", + "summary": "Check the live status of the service", + "parameters": [], + "responses": { + "200": { + "description": "Service is live" + } + }, + "tags": [ + "health" + ] + } + }, + "/readyz": { + "get": { + "operationId": "HealthController_readyz", + "summary": "Check the ready status of the service", + "parameters": [], + "responses": { + "200": { + "description": "Service is ready" + } + }, + "tags": [ + "health" + ] + } + }, + "/v1/scanner/reset": { + "post": { + "operationId": "ScanControllerV1_resetScanner", + "summary": "Reset blockchain scan to a specific block number or offset from the current position", + "parameters": [], + "requestBody": { + "required": true, + "description": "blockNumber", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResetScannerDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "scanner" + ] + } + }, + "/v1/scanner/options": { + "get": { + "operationId": "ScanControllerV1_getWatchOptions", + "summary": "Get the current watch options for the blockchain content event scanner", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChainWatchOptionsDto" + } + } + } + } + }, + "tags": [ + "scanner" + ] + }, + "post": { + "operationId": "ScanControllerV1_setWatchOptions", + "summary": "Set watch options to filter the blockchain content scanner by schemas or MSA IDs", + "parameters": [], + "requestBody": { + "required": true, + "description": "watchOptions: Filter contents by schemaIds and/or dsnpIds", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChainWatchOptionsDto" + } + } + } + }, + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "scanner" + ] + } + }, + "/v1/scanner/pause": { + "post": { + "operationId": "ScanControllerV1_pauseScanner", + "summary": "Pause the blockchain scanner", + "parameters": [], + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "scanner" + ] + } + }, + "/v1/scanner/start": { + "post": { + "operationId": "ScanControllerV1_startScanner", + "summary": "Resume the blockchain content event scanner", + "parameters": [ + { + "name": "immediate", + "required": false, + "in": "query", + "description": "immediate: whether to resume scan immediately (true), or wait until next scheduled scan (false)", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "201": { + "description": "" + } + }, + "tags": [ + "scanner" + ] + } + }, + "/v1/search": { + "post": { + "operationId": "SearchControllerV1_search", + "summary": "Search for DSNP content by id, start/end block, and filters", + "parameters": [], + "requestBody": { + "required": true, + "description": "Search for DSNP content by id, startBlock, endBlock, and filters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContentSearchRequestDto" + } + } + } + }, + "responses": { + "200": { + "description": "Returns a jobId to be used to retrieve the results", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + }, + "tags": [ + "search" + ] + } + }, + "/v1/webhooks": { + "put": { + "operationId": "WebhookControllerV1_registerWebhook", + "summary": "Register a webhook to be called when new content is encountered on the chain", + "parameters": [], + "requestBody": { + "required": true, + "description": "Register a webhook to be called when a new content is encountered", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/WebhookRegistrationDto" + } + } + } + }, + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "webhooks" + ] + }, + "delete": { + "operationId": "WebhookControllerV1_clearAllWebHooks", + "summary": "Clear all previously registered webhooks", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "webhooks" + ] + }, + "get": { + "operationId": "WebhookControllerV1_getRegisteredWebhooks", + "summary": "Get the list of currently registered webhooks", + "parameters": [], + "responses": { + "200": { + "description": "Returns a list of registered webhooks", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookRegistrationDto" + } + } + } + } + } + }, + "tags": [ + "webhooks" + ] + } + } + }, + "info": { + "title": "Content Watcher Service API", + "description": "Content Watcher Service API", + "version": "1.0", + "contact": {} + }, + "tags": [], + "servers": [], + "components": { + "securitySchemes": { + "bearer": { + "scheme": "bearer", + "bearerFormat": "JWT", + "type": "http", + "description": "Enter JWT token" + }, + "cookie": { + "type": "apiKey", + "in": "cookie", + "name": "SESSION" + } + }, + "schemas": { + "ResetScannerDto": { + "type": "object", + "properties": { + "blockNumber": { + "type": "number", + "minimum": 1, + "description": "The block number to reset the scanner to", + "example": 0 + }, + "rewindOffset": { + "type": "number", + "description": "Number of blocks to rewind the scanner to (from `blockNumber` if supplied; else from latest block", + "example": 100 + }, + "immediate": { + "type": "boolean", + "description": "Whether to schedule the new scan immediately or wait for the next scheduled interval", + "example": true + } + } + }, + "ChainWatchOptionsDto": { + "type": "object", + "properties": { + "schemaIds": { + "type": "array", + "items": { + "type": "number" + }, + "description": "Specific schema ids to watch for", + "example": [ + 1, + 19 + ] + }, + "dsnpIds": { + "description": "Specific dsnpIds (msa_id) to watch for", + "example": [ + "10074", + "100001" + ], + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "schemaIds", + "dsnpIds" + ] + }, + "ContentSearchRequestDto": { + "type": "object", + "properties": { + "startBlock": { + "type": "number", + "minimum": 1, + "description": "The starting block number to search from", + "example": 100 + }, + "endBlock": { + "type": "number", + "minimum": 1, + "description": "The ending block number to search to", + "example": 101 + }, + "filters": { + "description": "The schemaIds/dsnpIds to filter by", + "allOf": [ + { + "$ref": "#/components/schemas/ChainWatchOptionsDto" + } + ] + }, + "id": { + "type": "string" + } + }, + "required": [ + "startBlock", + "endBlock", + "filters", + "id" + ] + }, + "WebhookRegistrationDto": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "Webhook URL", + "example": "https://example.com/webhook" + }, + "announcementTypes": { + "description": "Announcement types to send to the webhook", + "example": [ + "Broadcast", + "Reaction", + "Tombstone", + "Reply", + "Update" + ], + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "url", + "announcementTypes" + ] + } + } + } +} \ No newline at end of file diff --git a/services/content-watcher/swagger.yaml b/services/content-watcher/swagger.yaml deleted file mode 100644 index d68496a6..00000000 --- a/services/content-watcher/swagger.yaml +++ /dev/null @@ -1,194 +0,0 @@ -openapi: 3.0.0 -paths: - /api/health: - get: - operationId: ApiController_health - parameters: [] - responses: - '200': - description: '' - /api/resetScanner: - post: - operationId: ApiController_resetScanner - parameters: [] - requestBody: - required: true - description: blockNumber - content: - application/json: - schema: - $ref: '#/components/schemas/ResetScannerDto' - responses: - '201': - description: '' - /api/setWatchOptions: - post: - operationId: ApiController_setWatchOptions - parameters: [] - requestBody: - required: true - description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds' - content: - application/json: - schema: - $ref: '#/components/schemas/ChainWatchOptionsDto' - responses: - '201': - description: '' - /api/pauseScanner: - post: - operationId: ApiController_pauseScanner - parameters: [] - responses: - '201': - description: '' - /api/startScanner: - post: - operationId: ApiController_startScanner - parameters: [] - responses: - '201': - description: '' - /api/search: - put: - operationId: ApiController_search - parameters: [] - requestBody: - required: true - description: Search for DSNP content by id, startBlock, endBlock, and filters - content: - application/json: - schema: - $ref: '#/components/schemas/ContentSearchRequestDto' - responses: - '200': - description: Returns a jobId to be used to retrieve the results - content: - application/json: - schema: - type: string - /api/registerWebhook: - put: - operationId: ApiController_registerWebhook - parameters: [] - requestBody: - required: true - description: Register a webhook to be called when a new content is created - content: - application/json: - schema: - $ref: '#/components/schemas/WebhookRegistrationDto' - responses: - '200': - description: '' - /api/clearAllWebHooks: - delete: - operationId: ApiController_clearAllWebHooks - parameters: [] - responses: - '200': - description: '' - /api/getRegisteredWebhooks: - get: - operationId: ApiController_getRegisteredWebhooks - parameters: [] - responses: - '200': - description: Returns a list of registered webhooks - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/WebhookRegistrationDto' -info: - title: Content Watcher Service API - description: Content Watcher Service API - version: '1.0' - contact: {} -tags: [] -servers: [] -components: - securitySchemes: - bearer: - scheme: bearer - bearerFormat: JWT - type: http - description: Enter JWT token - cookie: - type: apiKey - in: cookie - name: SESSION - schemas: - ResetScannerDto: - type: object - properties: - blockNumber: - format: int64 - type: integer - required: - - blockNumber - ChainWatchOptionsDto: - type: object - properties: - schemaIds: - description: Specific schema ids to watch for - example: - - '1' - - '19' - type: array - items: - type: string - dsnpIds: - description: Specific dsnpIds (msa_id) to watch for, these are the ids of the - publisher of the messages on frequency. If you want to watch for all messages - on frequency, leave this blank. - example: - - '10074' - - '100001' - type: array - items: - type: string - required: - - schemaIds - - dsnpIds - ContentSearchRequestDto: - type: object - properties: - startBlock: - type: string - description: The starting block number to search from - example: '100' - endBlock: - type: string - description: The ending block number to search to - example: '101' - filters: - description: The schemaIds/dsnpIds to filter by - allOf: - - $ref: '#/components/schemas/ChainWatchOptionsDto' - required: - - startBlock - - endBlock - - filters - WebhookRegistrationDto: - type: object - properties: - url: - type: string - description: Webhook URL - example: https://example.com/webhook - announcementTypes: - description: Announcement types to send to the webhook - example: - - Broadcast - - Reaction - - Tombstone - - Reply - - Update - type: array - items: - type: string - required: - - url - - announcementTypes diff --git a/services/content-watcher/tsconfig.json b/services/content-watcher/tsconfig.json index 8b70d256..8d296268 100644 --- a/services/content-watcher/tsconfig.json +++ b/services/content-watcher/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "allowSyntheticDefaultImports": true, - "baseUrl": "./src", + "baseUrl": "./", "esModuleInterop": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, @@ -11,13 +11,16 @@ "noImplicitThis": false, "outDir": "dist", "paths": { - "#app/*": [ - "*" + "#api": [ + "apps/api/src" ], - "@app/common": [ + "#api/*": [ + "apps/api/src/*" + ], + "@libs/common": [ "libs/common/src" ], - "@app/common/*": [ + "@libs/common/*": [ "libs/common/src/*" ] }, @@ -31,4 +34,4 @@ "node_modules/@types" ] } -} \ No newline at end of file +} From 12bf6f59f7fe5cd05654eeb1ba17cba1edff1212 Mon Sep 17 00:00:00 2001 From: Wil Wade Date: Fri, 28 Jun 2024 09:19:43 -0400 Subject: [PATCH 131/137] API Docs Ordering and generation command (#86) - Add the same swagger generation command from the other services - Add v1 prefix to tags - Reorder to the new standard - `npm run format` --- .../apps/api/src/api.module.ts | 8 +- .../apps/api/src/api.service.ts | 4 +- .../apps/api/src/build-openapi.ts | 35 +++++ .../api/src/controllers/health.controller.ts | 1 - .../src/controllers/v1/scanner.controller.ts | 8 +- .../src/controllers/v1/search.controller.ts | 3 +- .../src/controllers/v1/webhook.controller.ts | 2 +- .../content-watcher/apps/api/src/metadata.ts | 140 +++++++++++++++++- .../chain-event-processor.service.ts | 5 +- .../libs/common/src/config/swagger_config.ts | 8 +- .../libs/common/src/crawler/crawler.ts | 18 ++- .../src/interfaces/scan-reset.interface.ts | 6 +- .../libs/common/src/scanner/scanner.ts | 7 +- .../src/types/content-announcement/index.ts | 2 +- .../types/content-announcement/types.gen.ts | 78 +++++----- services/content-watcher/package.json | 3 +- services/content-watcher/swagger.json | 108 +++++++------- 17 files changed, 303 insertions(+), 133 deletions(-) create mode 100644 services/content-watcher/apps/api/src/build-openapi.ts diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index 57f41d6d..c8a16566 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -13,8 +13,8 @@ import { CrawlerModule } from '@libs/common/crawler/crawler.module'; import { IPFSProcessorModule } from '@libs/common/ipfs/ipfs.module'; import { PubSubModule } from '@libs/common/pubsub/pubsub.module'; import { ScannerModule } from '@libs/common/scanner/scanner.module'; -import { ConfigModule } from '@libs/common/config/config.module' -import { ConfigService } from '@libs/common/config/config.service' +import { ConfigModule } from '@libs/common/config/config.module'; +import { ConfigService } from '@libs/common/config/config.service'; import * as QueueConstants from '@libs/common'; @Module({ @@ -159,7 +159,9 @@ import * as QueueConstants from '@libs/common'; ScheduleModule.forRoot(), ], providers: [ApiService], - controllers: [HealthController, ScanControllerV1, SearchControllerV1, WebhookControllerV1], + // Controller order determines the order of display for docs + // v[Desc first][ABC Second], Health, and then Dev only last + controllers: [ScanControllerV1, SearchControllerV1, WebhookControllerV1, HealthController], exports: [], }) export class ApiModule {} diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index 3e506792..a41993f0 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -23,9 +23,9 @@ export class ApiService { this.logger = new Logger(this.constructor.name); } - public async getWatchOptions(): Promise { + public async getWatchOptions(): Promise { const options = await this.redis.get(EVENTS_TO_WATCH_KEY); - return options ? JSON.parse(options) as ChainWatchOptionsDto : null; + return options ? (JSON.parse(options) as ChainWatchOptionsDto) : null; } public async setWatchOptions(watchOptions: ChainWatchOptionsDto) { diff --git a/services/content-watcher/apps/api/src/build-openapi.ts b/services/content-watcher/apps/api/src/build-openapi.ts new file mode 100644 index 00000000..94a4b273 --- /dev/null +++ b/services/content-watcher/apps/api/src/build-openapi.ts @@ -0,0 +1,35 @@ +// This is a very hack way to generate the swagger +// without needing any of the service dependencies. +// At some point we should find a better way, but +// there might not be one. + +import '@frequency-chain/api-augment'; +import * as fs from 'fs'; +import { NestFactory } from '@nestjs/core'; + +// Mock out required env vars before the module loads +process.env.REDIS_URL = 'http://127.0.0.1'; +process.env.FREQUENCY_URL = 'http://127.0.0.1'; + +// eslint-disable-next-line +import { ApiModule } from './api.module'; +// eslint-disable-next-line +import { generateSwaggerDoc } from '../../../libs/common/src/config/swagger_config'; + +async function bootstrap() { + const app = await NestFactory.create(ApiModule, { + logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error'], + }); + + const document = await generateSwaggerDoc(app); + // write swagger.json to disk + fs.writeFileSync( + './swagger.json', + JSON.stringify(document, (_, v) => v, 2), + ); + console.log('OpenAPI written to ./swagger.json'); + // Do NOT call await app.close() as that requires a connection to Redis + process.exit(0); +} + +bootstrap(); diff --git a/services/content-watcher/apps/api/src/controllers/health.controller.ts b/services/content-watcher/apps/api/src/controllers/health.controller.ts index b15d3aa9..42a73b95 100644 --- a/services/content-watcher/apps/api/src/controllers/health.controller.ts +++ b/services/content-watcher/apps/api/src/controllers/health.controller.ts @@ -5,7 +5,6 @@ import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; @Controller() @ApiTags('health') export class HealthController { - // Health endpoint @Get('healthz') @HttpCode(HttpStatus.OK) diff --git a/services/content-watcher/apps/api/src/controllers/v1/scanner.controller.ts b/services/content-watcher/apps/api/src/controllers/v1/scanner.controller.ts index b44daea5..eda5d623 100644 --- a/services/content-watcher/apps/api/src/controllers/v1/scanner.controller.ts +++ b/services/content-watcher/apps/api/src/controllers/v1/scanner.controller.ts @@ -1,11 +1,11 @@ import { Body, Controller, Get, HttpException, HttpStatus, Logger, Post, Query } from '@nestjs/common'; import { ApiBody, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; -import { ApiService } from '#api/api.service' +import { ApiService } from '#api/api.service'; import { ResetScannerDto } from '@libs/common'; import { ChainWatchOptionsDto } from '@libs/common/dtos/chain.watch.dto'; @Controller('v1/scanner') -@ApiTags('scanner') +@ApiTags('v1/scanner') export class ScanControllerV1 { private readonly logger: Logger; @@ -14,7 +14,7 @@ export class ScanControllerV1 { } @Post('reset') - @ApiOperation({ summary: 'Reset blockchain scan to a specific block number or offset from the current position'}) + @ApiOperation({ summary: 'Reset blockchain scan to a specific block number or offset from the current position' }) @ApiBody({ description: 'blockNumber', type: ResetScannerDto, @@ -35,7 +35,7 @@ export class ScanControllerV1 { } @Post('options') - @ApiOperation({ summary: 'Set watch options to filter the blockchain content scanner by schemas or MSA IDs'}) + @ApiOperation({ summary: 'Set watch options to filter the blockchain content scanner by schemas or MSA IDs' }) @ApiBody({ description: 'watchOptions: Filter contents by schemaIds and/or dsnpIds', type: ChainWatchOptionsDto, diff --git a/services/content-watcher/apps/api/src/controllers/v1/search.controller.ts b/services/content-watcher/apps/api/src/controllers/v1/search.controller.ts index 8d0aa9ba..98a6f07a 100644 --- a/services/content-watcher/apps/api/src/controllers/v1/search.controller.ts +++ b/services/content-watcher/apps/api/src/controllers/v1/search.controller.ts @@ -5,9 +5,8 @@ import { Body, Controller, HttpStatus, Post } from '@nestjs/common'; import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; @Controller('v1/search') -@ApiTags('search') +@ApiTags('v1/search') export class SearchControllerV1 { - constructor(private readonly apiService: ApiService) {} @Post() diff --git a/services/content-watcher/apps/api/src/controllers/v1/webhook.controller.ts b/services/content-watcher/apps/api/src/controllers/v1/webhook.controller.ts index 23db3837..e711a6f5 100644 --- a/services/content-watcher/apps/api/src/controllers/v1/webhook.controller.ts +++ b/services/content-watcher/apps/api/src/controllers/v1/webhook.controller.ts @@ -4,7 +4,7 @@ import { Body, Controller, Delete, Get, HttpStatus, Logger, Put } from '@nestjs/ import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; @Controller('v1/webhooks') -@ApiTags('webhooks') +@ApiTags('v1/webhooks') export class WebhookControllerV1 { private readonly logger: Logger; diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index c036530e..8d74e832 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -1,9 +1,135 @@ /* eslint-disable */ export default async () => { - const t = { - ["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"), - ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto"), - ["../../../libs/common/src/dtos/chain.watch.dto"]: await import("../../../libs/common/src/dtos/chain.watch.dto") - }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } }, "ResetScannerDto": { blockNumber: { required: false, type: () => Number, minimum: 1 }, rewindOffset: { required: false, type: () => Number }, immediate: { required: false, type: () => Boolean } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String, minLength: 1, pattern: "DSNP_USER_URI_REGEX" } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String, pattern: "ISO8601_REGEX" }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String, pattern: "ISO8601_REGEX" } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String, pattern: "DSNP_CONTENT_HASH_REGEX" }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String, pattern: "DSNP_CONTENT_URI_REGEX" } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/common/src/dtos/chain.watch.dto"), { "ChainWatchOptionsDto": { schemaIds: { required: true, type: () => [Number] }, dsnpIds: { required: true, type: () => [String] } } }], [import("../../../libs/common/src/dtos/request-job.dto"), { "ContentSearchRequestDto": { id: { required: true, type: () => String }, startBlock: { required: true, type: () => Number, minimum: 1 }, endBlock: { required: true, type: () => Number, minimum: 1 }, filters: { required: true, type: () => t["../../../libs/common/src/dtos/chain.watch.dto"].ChainWatchOptionsDto } } }], [import("../../../libs/common/src/dtos/subscription.webhook.dto"), { "WebhookRegistrationDto": { url: { required: true, type: () => String }, announcementTypes: { required: true, type: () => [String] } } }]], "controllers": [[import("./controllers/health.controller"), { "HealthController": { "healthz": {}, "livez": {}, "readyz": {} } }], [import("./controllers/v1/scanner.controller"), { "ScanControllerV1": { "resetScanner": {}, "getWatchOptions": { type: t["../../../libs/common/src/dtos/chain.watch.dto"].ChainWatchOptionsDto }, "setWatchOptions": {}, "pauseScanner": {}, "startScanner": {} } }], [import("./controllers/v1/search.controller"), { "SearchControllerV1": { "search": {} } }], [import("./controllers/v1/webhook.controller"), { "WebhookControllerV1": { "registerWebhook": {}, "clearAllWebHooks": {}, "getRegisteredWebhooks": {} } }]] } }; -}; \ No newline at end of file + const t = { + ['../../../libs/common/src/dtos/activity.dto']: await import('../../../libs/common/src/dtos/activity.dto'), + ['../../../libs/common/src/dtos/announcement.dto']: await import('../../../libs/common/src/dtos/announcement.dto'), + ['../../../libs/common/src/dtos/chain.watch.dto']: await import('../../../libs/common/src/dtos/chain.watch.dto'), + }; + return { + '@nestjs/swagger': { + models: [ + [ + import('../../../libs/common/src/dtos/common.dto'), + { + DsnpUserIdParam: { userDsnpId: { required: true, type: () => String } }, + AnnouncementResponseDto: { referenceId: { required: true, type: () => String } }, + UploadResponseDto: { assetIds: { required: true, type: () => [String] } }, + FilesUploadDto: { files: { required: true, type: () => [Object] } }, + ResetScannerDto: { + blockNumber: { required: false, type: () => Number, minimum: 1 }, + rewindOffset: { required: false, type: () => Number }, + immediate: { required: false, type: () => Boolean }, + }, + }, + ], + [ + import('../../../libs/common/src/dtos/activity.dto'), + { + LocationDto: { + name: { required: true, type: () => String, minLength: 1 }, + accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, + altitude: { required: false, type: () => Number }, + latitude: { required: false, type: () => Number }, + longitude: { required: false, type: () => Number }, + radius: { required: false, type: () => Number, minimum: 0 }, + units: { required: false, enum: t['../../../libs/common/src/dtos/activity.dto'].UnitTypeDto }, + }, + AssetReferenceDto: { + referenceId: { required: true, type: () => String, minLength: 1 }, + height: { required: false, type: () => Number, minimum: 1 }, + width: { required: false, type: () => Number, minimum: 1 }, + duration: { required: false, type: () => String, pattern: 'DURATION_REGEX' }, + }, + TagDto: { + type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].TagTypeDto }, + name: { required: false, type: () => String, minLength: 1 }, + mentionedId: { required: false, type: () => String, minLength: 1, pattern: 'DSNP_USER_URI_REGEX' }, + }, + AssetDto: { + type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].AttachmentTypeDto }, + references: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, + name: { required: false, type: () => String, minLength: 1 }, + href: { required: false, type: () => String, minLength: 1 }, + }, + BaseActivityDto: { + name: { required: false, type: () => String }, + tag: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].TagDto] }, + location: { required: false, type: () => t['../../../libs/common/src/dtos/activity.dto'].LocationDto }, + }, + NoteActivityDto: { + content: { required: true, type: () => String, minLength: 1 }, + published: { required: true, type: () => String, pattern: 'ISO8601_REGEX' }, + assets: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetDto] }, + }, + ProfileActivityDto: { + icon: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, + summary: { required: false, type: () => String }, + published: { required: false, type: () => String, pattern: 'ISO8601_REGEX' }, + }, + }, + ], + [ + import('../../../libs/common/src/dtos/announcement.dto'), + { + BroadcastDto: { content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto } }, + ReplyDto: { + inReplyTo: { required: true, type: () => String, pattern: 'DSNP_CONTENT_URI_REGEX' }, + content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, + }, + TombstoneDto: { + targetContentHash: { required: true, type: () => String, pattern: 'DSNP_CONTENT_HASH_REGEX' }, + targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, + }, + UpdateDto: { + targetContentHash: { required: true, type: () => String, pattern: 'DSNP_CONTENT_HASH_REGEX' }, + targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, + content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, + }, + ReactionDto: { + emoji: { required: true, type: () => String, minLength: 1, pattern: 'DSNP_EMOJI_REGEX' }, + apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, + inReplyTo: { required: true, type: () => String, pattern: 'DSNP_CONTENT_URI_REGEX' }, + }, + ProfileDto: { profile: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].ProfileActivityDto } }, + }, + ], + [ + import('../../../libs/common/src/dtos/chain.watch.dto'), + { ChainWatchOptionsDto: { schemaIds: { required: true, type: () => [Number] }, dsnpIds: { required: true, type: () => [String] } } }, + ], + [ + import('../../../libs/common/src/dtos/request-job.dto'), + { + ContentSearchRequestDto: { + id: { required: true, type: () => String }, + startBlock: { required: true, type: () => Number, minimum: 1 }, + endBlock: { required: true, type: () => Number, minimum: 1 }, + filters: { required: true, type: () => t['../../../libs/common/src/dtos/chain.watch.dto'].ChainWatchOptionsDto }, + }, + }, + ], + [ + import('../../../libs/common/src/dtos/subscription.webhook.dto'), + { WebhookRegistrationDto: { url: { required: true, type: () => String }, announcementTypes: { required: true, type: () => [String] } } }, + ], + ], + controllers: [ + [import('./controllers/health.controller'), { HealthController: { healthz: {}, livez: {}, readyz: {} } }], + [ + import('./controllers/v1/scanner.controller'), + { + ScanControllerV1: { + resetScanner: {}, + getWatchOptions: { type: t['../../../libs/common/src/dtos/chain.watch.dto'].ChainWatchOptionsDto }, + setWatchOptions: {}, + pauseScanner: {}, + startScanner: {}, + }, + }, + ], + [import('./controllers/v1/search.controller'), { SearchControllerV1: { search: {} } }], + [import('./controllers/v1/webhook.controller'), { WebhookControllerV1: { registerWebhook: {}, clearAllWebHooks: {}, getRegisteredWebhooks: {} } }], + ], + }, + }; +}; diff --git a/services/content-watcher/libs/common/src/blockchain/chain-event-processor.service.ts b/services/content-watcher/libs/common/src/blockchain/chain-event-processor.service.ts index 803ce417..0e26d9be 100644 --- a/services/content-watcher/libs/common/src/blockchain/chain-event-processor.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/chain-event-processor.service.ts @@ -16,7 +16,7 @@ export class ChainEventProcessorService { public async getMessagesInBlock(blockNumber: number, filter?: ChainWatchOptionsDto): Promise { const blockHash = await this.blockchainService.getBlockHash(blockNumber); if (blockHash.isEmpty) { - return []; + return []; } const apiAt = await this.blockchainService.apiPromise.at(blockHash); const events = await apiAt.query.system.events(); @@ -92,7 +92,7 @@ export class ChainEventProcessorService { messageResponse.schemaId, message.cid.unwrap().toString(), message.index.toNumber(), - requestId + requestId, ); return { @@ -107,5 +107,4 @@ export class ChainEventProcessorService { await queue.addBulk(jobs); } } - } diff --git a/services/content-watcher/libs/common/src/config/swagger_config.ts b/services/content-watcher/libs/common/src/config/swagger_config.ts index 330a6bc0..e7617089 100644 --- a/services/content-watcher/libs/common/src/config/swagger_config.ts +++ b/services/content-watcher/libs/common/src/config/swagger_config.ts @@ -3,7 +3,7 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import * as fs from 'fs'; import metadata from '#api/metadata'; -export const initSwagger = async (app: INestApplication, apiPath: string) => { +export const generateSwaggerDoc = async (app: INestApplication) => { const options = new DocumentBuilder() .setTitle('Content Watcher Service API') .setDescription('Content Watcher Service API') @@ -15,9 +15,13 @@ export const initSwagger = async (app: INestApplication, apiPath: string) => { .addCookieAuth('SESSION') .build(); await SwaggerModule.loadPluginMetadata(metadata); - const document = SwaggerModule.createDocument(app, options, { + return SwaggerModule.createDocument(app, options, { extraModels: [], }); +}; + +export const initSwagger = async (app: INestApplication, apiPath: string) => { + const document = await generateSwaggerDoc(app); fs.writeFileSync( './swagger.json', JSON.stringify(document, (_, v) => v, 2), diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts index fa278dfe..4901a5ff 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.ts @@ -41,13 +41,15 @@ export class CrawlerService extends BaseConsumer { } private async processBlockList(id: string, blockList: number[], filters: ChainWatchOptionsDto) { - await Promise.all(blockList.map(async (blockNumber) => { - const messages = await this.chainEventService.getMessagesInBlock(blockNumber, filters); - if (messages.length > 0) { - this.logger.debug(`Found ${messages.length} messages for block ${blockNumber}`); - } - // eslint-disable-next-line no-await-in-loop - await this.chainEventService.queueIPFSJobs(messages, this.ipfsQueue, id); - })); + await Promise.all( + blockList.map(async (blockNumber) => { + const messages = await this.chainEventService.getMessagesInBlock(blockNumber, filters); + if (messages.length > 0) { + this.logger.debug(`Found ${messages.length} messages for block ${blockNumber}`); + } + // eslint-disable-next-line no-await-in-loop + await this.chainEventService.queueIPFSJobs(messages, this.ipfsQueue, id); + }), + ); } } diff --git a/services/content-watcher/libs/common/src/interfaces/scan-reset.interface.ts b/services/content-watcher/libs/common/src/interfaces/scan-reset.interface.ts index 01438395..c345349c 100644 --- a/services/content-watcher/libs/common/src/interfaces/scan-reset.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/scan-reset.interface.ts @@ -1,5 +1,5 @@ export interface IScanReset { - blockNumber?: number; - rewindOffset?: number; - immediate?: boolean; + blockNumber?: number; + rewindOffset?: number; + immediate?: boolean; } diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 3b807c81..77262503 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -47,7 +47,10 @@ export class ScannerService implements OnApplicationBootstrap, OnApplicationShut setImmediate(() => this.scan()); const scanInterval = this.configService.blockchainScanIntervalMinutes * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; - this.schedulerRegistry.addInterval(INTERVAL_SCAN_NAME, setInterval(() => this.scan(), scanInterval)); + this.schedulerRegistry.addInterval( + INTERVAL_SCAN_NAME, + setInterval(() => this.scan(), scanInterval), + ); } onApplicationShutdown(_signal?: string | undefined) { @@ -70,7 +73,7 @@ export class ScannerService implements OnApplicationBootstrap, OnApplicationShut public async resetScan({ blockNumber, rewindOffset, immediate }: IScanReset) { this.pauseScanner(); - let targetBlock = blockNumber ?? await this.blockchainService.getLatestFinalizedBlockNumber(); + let targetBlock = blockNumber ?? (await this.blockchainService.getLatestFinalizedBlockNumber()); targetBlock -= rewindOffset ? Math.abs(rewindOffset) : 0; targetBlock = Math.max(targetBlock, 1); this.scanResetBlockNumber = targetBlock; diff --git a/services/content-watcher/libs/common/src/types/content-announcement/index.ts b/services/content-watcher/libs/common/src/types/content-announcement/index.ts index 56bade12..343d3c8f 100644 --- a/services/content-watcher/libs/common/src/types/content-announcement/index.ts +++ b/services/content-watcher/libs/common/src/types/content-announcement/index.ts @@ -1,2 +1,2 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from './types.gen'; \ No newline at end of file +export * from './types.gen'; diff --git a/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts b/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts index eb3b3f93..9a05a885 100644 --- a/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts +++ b/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts @@ -1,66 +1,66 @@ // This file is auto-generated by @hey-api/openapi-ts export enum AnnouncementType { - Tombstone = 0, - Broadcast = 2, - Reply = 3, - Reaction = 4, - Profile = 5, - Update = 6, - PublicFollows = 113 + Tombstone = 0, + Broadcast = 2, + Reply = 3, + Reaction = 4, + Profile = 5, + Update = 6, + PublicFollows = 113, } export type AnnouncementResponse = { - /** - * An optional identifier for the request, may be used for tracking or correlation - */ - requestId?: string | null; - /** - * Identifier for the schema being used or referenced - */ - schemaId: number; - /** - * The block number on the blockchain where this announcement was recorded - */ - blockNumber: number; - announcement: TombstoneAnnouncement | BroadcastAnnouncement | ReplyAnnouncement | ReactionAnnouncement | ProfileAnnouncement | UpdateAnnouncement; + /** + * An optional identifier for the request, may be used for tracking or correlation + */ + requestId?: string | null; + /** + * Identifier for the schema being used or referenced + */ + schemaId: number; + /** + * The block number on the blockchain where this announcement was recorded + */ + blockNumber: number; + announcement: TombstoneAnnouncement | BroadcastAnnouncement | ReplyAnnouncement | ReactionAnnouncement | ProfileAnnouncement | UpdateAnnouncement; }; export type TypedAnnouncement = { - announcementType: AnnouncementType; - fromId: string; + announcementType: AnnouncementType; + fromId: string; }; export type TombstoneAnnouncement = TypedAnnouncement & { - targetAnnouncementType: number; - targetContentHash: string; + targetAnnouncementType: number; + targetContentHash: string; }; export type BroadcastAnnouncement = TypedAnnouncement & { - contentHash: string; - url: string; + contentHash: string; + url: string; }; export type ReplyAnnouncement = TypedAnnouncement & { - contentHash: string; - inReplyTo: string; - url: string; + contentHash: string; + inReplyTo: string; + url: string; }; export type ReactionAnnouncement = TypedAnnouncement & { - emoji: string; - inReplyTo: string; - apply: number; + emoji: string; + inReplyTo: string; + apply: number; }; export type ProfileAnnouncement = TypedAnnouncement & { - contentHash: string; - url: string; + contentHash: string; + url: string; }; export type UpdateAnnouncement = TypedAnnouncement & { - contentHash: string; - targetAnnouncementType: number; - targetContentHash: string; - url: string; -}; \ No newline at end of file + contentHash: string; + targetAnnouncementType: number; + targetContentHash: string; + url: string; +}; diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 92197d13..1a2da6ca 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -10,7 +10,8 @@ "start:api:dev": "set -a ; . .env ; nest start api --watch", "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", "build": "nest build", - "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", + "build:swagger": "npx ts-node -r tsconfig-paths/register apps/api/src/build-openapi.ts", + "build:metadata": "npx ts-node apps/api/src/generate-metadata.ts", "generate-swagger-ui": "npx --yes @redocly/cli build-docs swagger.json --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "docker-build": "docker build -t content-watcher-service .", diff --git a/services/content-watcher/swagger.json b/services/content-watcher/swagger.json index 2a684e05..f4d078f2 100644 --- a/services/content-watcher/swagger.json +++ b/services/content-watcher/swagger.json @@ -1,51 +1,6 @@ { "openapi": "3.0.0", "paths": { - "/healthz": { - "get": { - "operationId": "HealthController_healthz", - "summary": "Check the health status of the service", - "parameters": [], - "responses": { - "200": { - "description": "Service is healthy" - } - }, - "tags": [ - "health" - ] - } - }, - "/livez": { - "get": { - "operationId": "HealthController_livez", - "summary": "Check the live status of the service", - "parameters": [], - "responses": { - "200": { - "description": "Service is live" - } - }, - "tags": [ - "health" - ] - } - }, - "/readyz": { - "get": { - "operationId": "HealthController_readyz", - "summary": "Check the ready status of the service", - "parameters": [], - "responses": { - "200": { - "description": "Service is ready" - } - }, - "tags": [ - "health" - ] - } - }, "/v1/scanner/reset": { "post": { "operationId": "ScanControllerV1_resetScanner", @@ -68,7 +23,7 @@ } }, "tags": [ - "scanner" + "v1/scanner" ] } }, @@ -90,7 +45,7 @@ } }, "tags": [ - "scanner" + "v1/scanner" ] }, "post": { @@ -114,7 +69,7 @@ } }, "tags": [ - "scanner" + "v1/scanner" ] } }, @@ -129,7 +84,7 @@ } }, "tags": [ - "scanner" + "v1/scanner" ] } }, @@ -154,7 +109,7 @@ } }, "tags": [ - "scanner" + "v1/scanner" ] } }, @@ -187,7 +142,7 @@ } }, "tags": [ - "search" + "v1/search" ] } }, @@ -213,7 +168,7 @@ } }, "tags": [ - "webhooks" + "v1/webhooks" ] }, "delete": { @@ -226,7 +181,7 @@ } }, "tags": [ - "webhooks" + "v1/webhooks" ] }, "get": { @@ -249,7 +204,52 @@ } }, "tags": [ - "webhooks" + "v1/webhooks" + ] + } + }, + "/healthz": { + "get": { + "operationId": "HealthController_healthz", + "summary": "Check the health status of the service", + "parameters": [], + "responses": { + "200": { + "description": "Service is healthy" + } + }, + "tags": [ + "health" + ] + } + }, + "/livez": { + "get": { + "operationId": "HealthController_livez", + "summary": "Check the live status of the service", + "parameters": [], + "responses": { + "200": { + "description": "Service is live" + } + }, + "tags": [ + "health" + ] + } + }, + "/readyz": { + "get": { + "operationId": "HealthController_readyz", + "summary": "Check the ready status of the service", + "parameters": [], + "responses": { + "200": { + "description": "Service is ready" + } + }, + "tags": [ + "health" ] } } From 5551680ccf9a266d9a69df0ec6a2edab7a600ad6 Mon Sep 17 00:00:00 2001 From: Matthew Orris <1466844+mattheworris@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:38:06 -0400 Subject: [PATCH 132/137] Published container not working with social-app-template (#93) The `content-watcher-service` fails to execute when used with `social-app-template` because it can't find `/usr/bin/tini`. ## Solution: `tini` on Alpine lives at `/sbin/tini`. This results in the following error: ```bash 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [NestFactory] Starting Nest application... 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] EventEmitterModule dependencies initialized +22ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] ConfigHostModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardFeatureModule dependencies initialized +1ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardFeatureModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardFeatureModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardFeatureModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardFeatureModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardFeatureModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardFeatureModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardFeatureModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] DiscoveryModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullBoardRootModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] ConfigModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] ScheduleModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] EventEmitterModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] ConfigModule dependencies initialized +1ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BlockchainModule dependencies initialized +2ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullModule dependencies initialized +3ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] BullModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] RedisModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] IPFSProcessorModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] ScannerModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] CrawlerModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] PubSubModule dependencies initialized +0ms 2024-07-02 16:22:04 [Nest] 7 - 07/02/2024, 10:22:04 PM LOG [InstanceLoader] ApiModule dependencies initialized +0ms 2024-07-02 16:22:04 /app/node_modules/@nestjs/bullmq/dist/hosts/worker-host.class.js:7 2024-07-02 16:22:04 throw new Error('"Worker" has not yet been initialized. Make sure to interact with worker instances after the "onModuleInit" lifecycle hook is triggered, for example, in the "onApplicationBootstrap" hook.'); 2024-07-02 16:22:04 ^ 2024-07-02 16:22:04 2024-07-02 16:22:04 Error: "Worker" has not yet been initialized. Make sure to interact with worker instances after the "onModuleInit" lifecycle hook is triggered, for example, in the "onApplicationBootstrap" hook. 2024-07-02 16:22:04 at get worker [as worker] (/app/node_modules/@nestjs/bullmq/dist/hosts/worker-host.class.js:7:19) 2024-07-02 16:22:04 at CrawlerService.onModuleDestroy (/app/dist/apps/api/main.js:2602:20) 2024-07-02 16:22:04 at MapIterator.iteratee (/app/node_modules/@nestjs/core/hooks/on-module-destroy.hook.js:22:43) 2024-07-02 16:22:04 at MapIterator.next (/app/node_modules/iterare/lib/map.js:13:39) 2024-07-02 16:22:04 at IteratorWithOperators.next (/app/node_modules/iterare/lib/iterate.js:21:28) 2024-07-02 16:22:04 at Function.from () 2024-07-02 16:22:04 at IteratorWithOperators.toArray (/app/node_modules/iterare/lib/iterate.js:180:22) 2024-07-02 16:22:04 at callOperator (/app/node_modules/@nestjs/core/hooks/on-module-destroy.hook.js:23:10) 2024-07-02 16:22:04 at callModuleDestroyHook (/app/node_modules/@nestjs/core/hooks/on-module-destroy.hook.js:43:23) 2024-07-02 16:22:04 at NestApplication.callDestroyHook (/app/node_modules/@nestjs/core/nest-application-context.js:233:53) 2024-07-02 16:22:04 2024-07-02 16:22:04 Node.js v20.12.2 ``` ## Changes: - `Dockerfile` rolled back to Node:Ubuntu/Debian base image - Add environment to `docker-compose.yaml` --- services/content-watcher/Dockerfile | 73 +++++--------------- services/content-watcher/docker-compose.yaml | 17 ++++- 2 files changed, 32 insertions(+), 58 deletions(-) diff --git a/services/content-watcher/Dockerfile b/services/content-watcher/Dockerfile index 26fd1aa1..2922244d 100644 --- a/services/content-watcher/Dockerfile +++ b/services/content-watcher/Dockerfile @@ -1,72 +1,33 @@ -# syntax=docker/dockerfile:1 +# Use a multi-stage build for efficiency +FROM node:20 AS builder -# Comments are provided throughout this file to help you get started. -# If you need more help, visit the Dockerfile reference guide at -# https://docs.docker.com/go/dockerfile-reference/ - -# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7 - -ARG NODE_VERSION=20 - -################################################################################ -# Use node image for base image for all stages. -FROM node:${NODE_VERSION}-alpine as base - -# Set working directory for all build stages. WORKDIR /app +COPY package*.json ./ -################################################################################ -# Create a stage for installing production dependecies. -FROM base as deps - -# Download dependencies as a separate step to take advantage of Docker's caching. -# Leverage a cache mount to /root/.npm to speed up subsequent builds. -# Leverage bind mounts to package.json and package-lock.json to avoid having to copy them -# into this layer. -RUN --mount=type=bind,source=package.json,target=package.json \ - --mount=type=bind,source=package-lock.json,target=package-lock.json \ - --mount=type=cache,target=/root/.npm \ - npm ci --omit=dev +RUN npm ci -################################################################################ -# Create a stage for building the application. -FROM deps as build - -# Download additional development dependencies before building, as some projects require -# "devDependencies" to be installed to build. If you don't need this, remove this step. -RUN --mount=type=bind,source=package.json,target=package.json \ - --mount=type=bind,source=package-lock.json,target=package-lock.json \ - --mount=type=cache,target=/root/.npm \ - npm ci - -# Copy the rest of the source files into the image. COPY . . -# Run the build script. -RUN npm run build -################################################################################ -# Create a new stage to run the application with minimal runtime dependencies -# where the necessary files are copied from the build stage. -FROM base as final +# Build the application +RUN npm run build -# Use production node environment by default. -ENV NODE_ENV production +# Production stage +FROM node:20 -# Run the application as a non-root user. -USER node +WORKDIR /app -# Copy package.json so that package manager commands can be used. -COPY package.json . +COPY --from=builder /app/dist ./dist +COPY package*.json ./ -# Copy the production dependencies from the deps stage and also -# the built application from the build stage into the image. -COPY --from=deps /app/node_modules ./node_modules -COPY --from=build /app/dist ./dist +RUN npm ci --omit=dev +# We want jq and curl in the final image, but we don't need the support files +RUN apt-get update && \ + apt-get install -y jq curl tini && \ + apt-get clean && \ + rm -rf /usr/share/doc /usr/share/man /usr/share/zsh -# Expose the port that the application listens on. EXPOSE 3000 -# Run the application. ENTRYPOINT ["/usr/bin/tini", "--", "node", "dist/apps/api/main.js"] diff --git a/services/content-watcher/docker-compose.yaml b/services/content-watcher/docker-compose.yaml index b3f4b6a0..4201450d 100644 --- a/services/content-watcher/docker-compose.yaml +++ b/services/content-watcher/docker-compose.yaml @@ -1,3 +1,16 @@ +x-content-watcher-environment: &content-watcher-environment + IPFS_GATEWAY_URL: 'https://ipfs:8080/ipfs/[CID]' + FREQUENCY_URL: 'ws://frequency:9944' + STARTING_BLOCK: 1 + REDIS_URL: 'redis://redis:6379' + BLOCKCHAIN_SCAN_INTERVAL_MINUTES: 1 + QUEUE_HIGH_WATER: 1000 + WEBHOOK_FAILURE_THRESHOLD: 4 + WEBHOOK_RETRY_INTERVAL_SECONDS: 10 + API_PORT: 3000 + CAPACITY_LIMIT: '{"type":"percentage", "value":80}' + IPFS_ENDPOINT: 'http://ipfs:5001' + services: redis: image: redis:latest @@ -82,8 +95,8 @@ services: dockerfile: dev.Dockerfile tags: - content-watcher-service:latest - env_file: - - .env.docker.dev + environment: + << : *content-watcher-environment ports: - 3000:3000 volumes: From 9d67c24a6187ec93a6c4fda7113a8f2b665807db Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Thu, 11 Jul 2024 09:22:40 -0400 Subject: [PATCH 133/137] feat: specify chain scan interval in seconds instead of minutes (#99) Change `BLOCKCHAIN_SCAN_INTERVAL_MINUTES` to `BLOCKCHAIN_SCAN_INTERVAL_SECONDS` --- services/content-watcher/.env.docker.dev | 6 +++--- services/content-watcher/ENVIRONMENT.md | 2 +- services/content-watcher/INSTALLING.md | 2 +- services/content-watcher/docker-compose.yaml | 10 +++++----- services/content-watcher/env.template | 6 +++--- .../libs/common/src/config/config.service.spec.ts | 12 ++++++------ .../libs/common/src/config/config.service.ts | 7 ++++--- .../libs/common/src/config/env.config.ts | 4 ++-- .../libs/common/src/scanner/scanner.ts | 2 +- 9 files changed, 26 insertions(+), 25 deletions(-) diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.docker.dev index 953de96f..4f91a488 100644 --- a/services/content-watcher/.env.docker.dev +++ b/services/content-watcher/.env.docker.dev @@ -24,9 +24,9 @@ STARTING_BLOCK=1 # Redis URL REDIS_URL=redis://redis:6379 -# How many minutes to delay between successive scans of the chain -# for new accounts (after end of chain is reached) -BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 +# How many seconds to delay between successive scans of the chain +# for new content (after end of chain is reached) +BLOCKCHAIN_SCAN_INTERVAL_SECONDS=12 # Max number of jobs allowed on the queue before # blockchain scan will be paused to allow queue to drain diff --git a/services/content-watcher/ENVIRONMENT.md b/services/content-watcher/ENVIRONMENT.md index 55c3fa13..cd9ed3fc 100644 --- a/services/content-watcher/ENVIRONMENT.md +++ b/services/content-watcher/ENVIRONMENT.md @@ -5,7 +5,7 @@ This application recognizes the following environment variables: | Name | Description | Range/Type | Required? | Default | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ | :--------------------: | :-------: | :-----: | | `API_PORT` | HTTP port that the application listens on | 1025 - 65535 | | 3000 | -| `BLOCKCHAIN_SCAN_INTERVAL_MINUTES` | How many minutes to delay between successive scans of the chain for new accounts (after end of chain is reached) | > 0 | | 180 | +| `BLOCKCHAIN_SCAN_INTERVAL_SECONDS` | How many seconds to delay between successive scans of the chain for new content (after end of chain is reached) | > 0 | | 12 | | `FREQUENCY_URL` | Blockchain node address | http(s): or ws(s): URL | Y | | | `IPFS_BASIC_AUTH_SECRET` | If required for read requests, put Infura auth token here, or leave blank for default Kubo RPC | string | N | blank | | `IPFS_BASIC_AUTH_USER` | If required for read requests, put Infura Project ID here, or leave blank for default Kubo RPC | string | N | blank | diff --git a/services/content-watcher/INSTALLING.md b/services/content-watcher/INSTALLING.md index dba6620f..21503830 100644 --- a/services/content-watcher/INSTALLING.md +++ b/services/content-watcher/INSTALLING.md @@ -61,5 +61,5 @@ The following is a list of environment variables that may be set to control the |`FREQUENCY_URL`|**yes**|Blockchain URL|_none_| |`STARTING_BLOCK`|**maybe**|Starting block for scanner to scan from|_none_| |`REDIS_URL`|**yes**|URL used to connect to Redis instance|_none_
\*preset to the internal Redis URL in the standalone container| -|`BLOCKCHAIN_SCAN_INTERVAL_MINUTES`|no|# of minutes to wait in between scans of the blockchain|180| +|`BLOCKCHAIN_SCAN_INTERVAL_SECONDS`|no|# of seconds to wait in between scans of the blockchain|12| |`QUEUE_HIGH_WATER`|no|# of pending queue entries to allow before pausing blockchain scanning until the next scan cycle|1000| diff --git a/services/content-watcher/docker-compose.yaml b/services/content-watcher/docker-compose.yaml index 4201450d..9ca71688 100644 --- a/services/content-watcher/docker-compose.yaml +++ b/services/content-watcher/docker-compose.yaml @@ -3,14 +3,14 @@ x-content-watcher-environment: &content-watcher-environment FREQUENCY_URL: 'ws://frequency:9944' STARTING_BLOCK: 1 REDIS_URL: 'redis://redis:6379' - BLOCKCHAIN_SCAN_INTERVAL_MINUTES: 1 + BLOCKCHAIN_SCAN_INTERVAL_SECONDS: 12 QUEUE_HIGH_WATER: 1000 WEBHOOK_FAILURE_THRESHOLD: 4 WEBHOOK_RETRY_INTERVAL_SECONDS: 10 API_PORT: 3000 CAPACITY_LIMIT: '{"type":"percentage", "value":80}' IPFS_ENDPOINT: 'http://ipfs:5001' - + services: redis: image: redis:latest @@ -28,9 +28,9 @@ services: platform: linux/amd64 # Uncomment SEALING_MODE and SEALING_INTERVAL if you want to use interval sealing. # Other options you may want to add depending on your test scenario. - # environment: - # - SEALING_MODE=interval - # - SEALING_INTERVAL=3 + environment: + - SEALING_MODE=interval + - SEALING_INTERVAL=1 # - CREATE_EMPTY_BLOCKS=true # Uncomment below if you want to let the chain run and keep all of the historical blocks # command: --state-pruning=archive diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index e0e17d08..e6f596c6 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -23,9 +23,9 @@ STARTING_BLOCK=1 # Redis URL REDIS_URL=redis://0.0.0.0:6379 -# How many minutes to delay between successive scans of the chain -# for new accounts (after end of chain is reached) -BLOCKCHAIN_SCAN_INTERVAL_MINUTES=1 +# How many seconds to delay between successive scans of the chain +# for new content (after end of chain is reached) +BLOCKCHAIN_SCAN_INTERVAL_SECONDS=12 # Max number of jobs allowed on the queue before # blockchain scan will be paused to allow queue to drain diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index 337d8a74..abec7d55 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -40,7 +40,7 @@ describe('ContentWatcherConfigService', () => { IPFS_GATEWAY_URL: undefined, IPFS_BASIC_AUTH_USER: undefined, IPFS_BASIC_AUTH_SECRET: undefined, - BLOCKCHAIN_SCAN_INTERVAL_MINUTES: undefined, + BLOCKCHAIN_SCAN_INTERVAL_SECONDS: undefined, QUEUE_HIGH_WATER: undefined, WEBHOOK_FAILURE_THRESHOLD: undefined, WEBHOOK_RETRY_INTERVAL_SECONDS: undefined, @@ -75,10 +75,10 @@ describe('ContentWatcherConfigService', () => { }); it('invalid scan interval should fail', async () => { - const { BLOCKCHAIN_SCAN_INTERVAL_MINUTES: dummy, ...env } = ALL_ENV; - await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_MINUTES: -1, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_MINUTES: 0, ...env })).rejects.toBeDefined(); - await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_MINUTES: 'foo', ...env })).rejects.toBeDefined(); + const { BLOCKCHAIN_SCAN_INTERVAL_SECONDS: dummy, ...env } = ALL_ENV; + await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_SECONDS: -1, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_SECONDS: 0, ...env })).rejects.toBeDefined(); + await expect(setupConfigService({ BLOCKCHAIN_SCAN_INTERVAL_SECONDS: 'foo', ...env })).rejects.toBeDefined(); }); it('invalid queue high water should fail', async () => { @@ -113,7 +113,7 @@ describe('ContentWatcherConfigService', () => { }); it('should get scan interval', () => { - expect(contentWatcherConfigService.blockchainScanIntervalMinutes).toStrictEqual(parseInt(ALL_ENV.BLOCKCHAIN_SCAN_INTERVAL_MINUTES as string, 10)); + expect(contentWatcherConfigService.blockchainScanIntervalSeconds).toStrictEqual(parseInt(ALL_ENV.BLOCKCHAIN_SCAN_INTERVAL_SECONDS as string)); }); it('should get queue high water mark', () => { diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index 63a26a48..ade4aa83 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* https://docs.nestjs.com/providers#services */ @@ -13,7 +14,7 @@ export interface ConfigEnvironmentVariables { REDIS_URL: URL; FREQUENCY_URL: URL; STARTING_BLOCK: string; - BLOCKCHAIN_SCAN_INTERVAL_MINUTES: number; + BLOCKCHAIN_SCAN_INTERVAL_SECONDS: number; QUEUE_HIGH_WATER: number; WEBHOOK_FAILURE_THRESHOLD: number; WEBHOOK_RETRY_INTERVAL_SECONDS: number; @@ -41,8 +42,8 @@ export class ConfigService { return this.nestConfigService.get('STARTING_BLOCK')!; } - public get blockchainScanIntervalMinutes(): number { - return this.nestConfigService.get('BLOCKCHAIN_SCAN_INTERVAL_MINUTES') ?? 1; + public get blockchainScanIntervalSeconds(): number { + return this.nestConfigService.get('BLOCKCHAIN_SCAN_INTERVAL_SECONDS') ?? 12; } public get queueHighWater(): number { diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index 90feb804..b158bf25 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -11,9 +11,9 @@ export const configModuleOptions: ConfigModuleOptions = { REDIS_URL: Joi.string().uri().required(), FREQUENCY_URL: Joi.string().uri().required(), STARTING_BLOCK: Joi.number().min(1), - BLOCKCHAIN_SCAN_INTERVAL_MINUTES: Joi.number() + BLOCKCHAIN_SCAN_INTERVAL_SECONDS: Joi.number() .min(1) - .default(3 * 60), + .default(12), QUEUE_HIGH_WATER: Joi.number().min(100).default(1000), WEBHOOK_FAILURE_THRESHOLD: Joi.number().min(0).default(3), WEBHOOK_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(10), diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.ts index 77262503..341fbeed 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.ts @@ -46,7 +46,7 @@ export class ScannerService implements OnApplicationBootstrap, OnApplicationShut } setImmediate(() => this.scan()); - const scanInterval = this.configService.blockchainScanIntervalMinutes * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND; + const scanInterval = this.configService.blockchainScanIntervalSeconds * MILLISECONDS_PER_SECOND; this.schedulerRegistry.addInterval( INTERVAL_SCAN_NAME, setInterval(() => this.scan(), scanInterval), From c788dc8468f3cec85573492ff0ee63785d17cd76 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:06:20 +0000 Subject: [PATCH 134/137] Bump @nestjs/config from 3.2.2 to 3.2.3 (#89) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@nestjs/config](https://github.com/nestjs/config) from 3.2.2 to 3.2.3.
Commits
  • 5133c72 chore(): release v3.2.3
  • faa8f59 Merge branch 'Motii1-remove-unnecessary-uuid'
  • 0045924 chore: resolve conflicts
  • f039417 Merge pull request #1752 from nestjs/renovate/nest-monorepo
  • 00febb5 chore(deps): update nest monorepo to v10.3.10
  • b2ef5a8 Merge pull request #1698 from nestjs/renovate/cimg-node-22.x
  • 81534ba Merge pull request #1751 from nestjs/renovate/release-it-17.x
  • ea180e4 Merge pull request #1746 from micalevisk/patch-1
  • 3b03bd9 chore(deps): update dependency release-it to v17.4.1
  • 112b556 chore(deps): update dependency @​types/lodash to v4.17.6
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@nestjs/config&package-manager=npm_and_yarn&previous-version=3.2.2&new-version=3.2.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- services/content-watcher/package-lock.json | 11 +++++------ services/content-watcher/package.json | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index e3f2d05a..1e2aee8e 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -21,7 +21,7 @@ "@nestjs/bullmq": "^10.1.1", "@nestjs/cli": "^10.3.2", "@nestjs/common": "^10.3.8", - "@nestjs/config": "^3.2.2", + "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.3.8", "@nestjs/event-emitter": "^2.0.4", "@nestjs/platform-express": "^10.3.8", @@ -3111,14 +3111,13 @@ } }, "node_modules/@nestjs/config": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.2.tgz", - "integrity": "sha512-vGICPOui5vE6kPz1iwQ7oCnp3qWgqxldPmBQ9onkVoKlBtyc83KJCr7CjuVtf4OdovMAVcux1d8Q6jglU2ZphA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.3.tgz", + "integrity": "sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w==", "dependencies": { "dotenv": "16.4.5", "dotenv-expand": "10.0.0", - "lodash": "4.17.21", - "uuid": "9.0.1" + "lodash": "4.17.21" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 1a2da6ca..9c639692 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -52,7 +52,7 @@ "@nestjs/bullmq": "^10.1.1", "@nestjs/cli": "^10.3.2", "@nestjs/common": "^10.3.8", - "@nestjs/config": "^3.2.2", + "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.3.8", "@nestjs/event-emitter": "^2.0.4", "@nestjs/platform-express": "^10.3.8", From 246e4a77d012a3c52afddf410ac7262810d77525 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:08:21 +0000 Subject: [PATCH 135/137] Bump @nestjs/testing from 10.3.8 to 10.3.10 (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [@nestjs/testing](https://github.com/nestjs/nest/tree/HEAD/packages/testing) from 10.3.8 to 10.3.10.
Release notes

Sourced from @​nestjs/testing's releases.

v10.3.9 (2024-06-03)

Bug fixes

Enhancements

Docs

Dependencies

... (truncated)

Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@nestjs/testing&package-manager=npm_and_yarn&previous-version=10.3.8&new-version=10.3.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- services/content-watcher/package-lock.json | 16 +++++++++++----- services/content-watcher/package.json | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index 1e2aee8e..f2ec4336 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -49,7 +49,7 @@ "devDependencies": { "@hey-api/openapi-ts": "^0.45.1", "@jest/globals": "^29.7.0", - "@nestjs/testing": "^10.3.8", + "@nestjs/testing": "^10.3.10", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/node": "^20.12.10", @@ -3278,12 +3278,12 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.3.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.8.tgz", - "integrity": "sha512-hpX9das2TdFTKQ4/2ojhjI6YgXtCfXRKui3A4Qaj54VVzc5+mtK502Jj18Vzji98o9MVS6skmYu+S/UvW3U6Fw==", + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.10.tgz", + "integrity": "sha512-i3HAtVQJijxNxJq1k39aelyJlyEIBRONys7IipH/4r8W0J+M1V+y5EKDOyi4j1SdNSb/vmNyWpZ2/ewZjl3kRA==", "dev": true, "dependencies": { - "tslib": "2.6.2" + "tslib": "2.6.3" }, "funding": { "type": "opencollective", @@ -3304,6 +3304,12 @@ } } }, + "node_modules/@nestjs/testing/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/@noble/curves": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz", diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 9c639692..6946fb0f 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -80,7 +80,7 @@ "devDependencies": { "@hey-api/openapi-ts": "^0.45.1", "@jest/globals": "^29.7.0", - "@nestjs/testing": "^10.3.8", + "@nestjs/testing": "^10.3.10", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/node": "^20.12.10", From b0cbd86edbc76c5950e9487f2f48b8a5f3dd051a Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Fri, 19 Jul 2024 12:09:51 -0400 Subject: [PATCH 136/137] 102 historical content scan should not impede current content scan (#103) This PR does the following: - add cache prefix for Redis and BullMQ keys to avoid conflicts with other apps - refactor NestJS modules to avoid multiple instantiations - add webhook URL to search request and propagate through queues - Remove STARTING_BLOCK env; scan should always start from last known scan position, or end-of-chain if no cached position - Fix search crawler to process correct block list - update OpenAPI generated document - process search block list in chunks to avoid OOM Closes #102 --- services/content-watcher/.env.docker.dev | 3 - services/content-watcher/ENVIRONMENT.md | 29 +-- .../apps/api/src/api.module.ts | 112 +++------- .../apps/api/src/api.service.ts | 15 +- services/content-watcher/apps/api/src/main.ts | 4 +- .../content-watcher/apps/api/src/metadata.ts | 7 +- services/content-watcher/dev.Dockerfile | 2 +- services/content-watcher/docker-compose.yaml | 2 +- services/content-watcher/env.template | 6 +- .../src/blockchain/blockchain.module.ts | 7 +- .../src/blockchain/blockchain.service.ts | 8 +- .../libs/common/src/config/config.module.ts | 15 +- .../common/src/config/config.service.spec.ts | 16 +- .../libs/common/src/config/config.service.ts | 20 +- .../libs/common/src/config/env.config.ts | 6 +- .../libs/common/src/crawler/crawler.module.ts | 61 +---- .../common/src/crawler/crawler.service.ts | 78 +++++++ .../libs/common/src/crawler/crawler.ts | 55 ----- .../libs/common/src/dtos/chain.watch.dto.ts | 2 + .../src/dtos/content-search-request.dto.ts | 46 ++++ .../libs/common/src/dtos/request-job.dto.ts | 31 --- .../src/dtos/subscription.webhook.dto.ts | 8 +- .../content-watcher/libs/common/src/index.ts | 4 +- .../announcement-subscription.interface.ts | 4 + .../src/interfaces/ipfs.job.interface.ts | 5 +- .../libs/common/src/ipfs/ipfs.dsnp.ts | 211 +++++++----------- .../libs/common/src/ipfs/ipfs.module.ts | 130 +---------- .../common/src/pubsub/announcers/broadcast.ts | 2 +- .../common/src/pubsub/announcers/profile.ts | 2 +- .../common/src/pubsub/announcers/reaction.ts | 2 +- .../common/src/pubsub/announcers/reply.ts | 2 +- .../common/src/pubsub/announcers/tombstone.ts | 2 +- .../common/src/pubsub/announcers/update.ts | 2 +- .../libs/common/src/pubsub/pubsub.module.ts | 130 +---------- .../libs/common/src/pubsub/pubsub.service.ts | 24 +- .../queues.ts => queues/queue-constants.ts} | 0 .../libs/common/src/queues/queue.module.ts | 125 +++++++++++ .../libs/common/src/scanner/scanner.module.ts | 50 +---- .../{scanner.ts => scanner.service.ts} | 31 ++- .../types/content-announcement/types.gen.ts | 6 +- .../libs/common/src/utils/ipfs.client.ts | 4 +- .../libs/common/src/utils/type-guards.ts | 41 ++++ services/content-watcher/package.json | 10 +- services/content-watcher/swagger.json | 27 ++- 44 files changed, 552 insertions(+), 795 deletions(-) create mode 100644 services/content-watcher/libs/common/src/crawler/crawler.service.ts delete mode 100644 services/content-watcher/libs/common/src/crawler/crawler.ts create mode 100644 services/content-watcher/libs/common/src/dtos/content-search-request.dto.ts delete mode 100644 services/content-watcher/libs/common/src/dtos/request-job.dto.ts create mode 100644 services/content-watcher/libs/common/src/interfaces/announcement-subscription.interface.ts rename services/content-watcher/libs/common/src/{utils/queues.ts => queues/queue-constants.ts} (100%) create mode 100644 services/content-watcher/libs/common/src/queues/queue.module.ts rename services/content-watcher/libs/common/src/scanner/{scanner.ts => scanner.service.ts} (86%) create mode 100644 services/content-watcher/libs/common/src/utils/type-guards.ts diff --git a/services/content-watcher/.env.docker.dev b/services/content-watcher/.env.docker.dev index 4f91a488..32474bf2 100644 --- a/services/content-watcher/.env.docker.dev +++ b/services/content-watcher/.env.docker.dev @@ -18,9 +18,6 @@ IPFS_GATEWAY_URL="http://ipfs:8080/ipfs/[CID]" # Blockchain node address FREQUENCY_URL=ws://frequency:9944 -# Block number from which the service will start scanning the chain -STARTING_BLOCK=1 - # Redis URL REDIS_URL=redis://redis:6379 diff --git a/services/content-watcher/ENVIRONMENT.md b/services/content-watcher/ENVIRONMENT.md index cd9ed3fc..811af674 100644 --- a/services/content-watcher/ENVIRONMENT.md +++ b/services/content-watcher/ENVIRONMENT.md @@ -2,17 +2,18 @@ This application recognizes the following environment variables: -| Name | Description | Range/Type | Required? | Default | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ | :--------------------: | :-------: | :-----: | -| `API_PORT` | HTTP port that the application listens on | 1025 - 65535 | | 3000 | -| `BLOCKCHAIN_SCAN_INTERVAL_SECONDS` | How many seconds to delay between successive scans of the chain for new content (after end of chain is reached) | > 0 | | 12 | -| `FREQUENCY_URL` | Blockchain node address | http(s): or ws(s): URL | Y | | -| `IPFS_BASIC_AUTH_SECRET` | If required for read requests, put Infura auth token here, or leave blank for default Kubo RPC | string | N | blank | -| `IPFS_BASIC_AUTH_USER` | If required for read requests, put Infura Project ID here, or leave blank for default Kubo RPC | string | N | blank | -| `IPFS_ENDPOINT` | URL to IPFS endpoint | URL | Y | | -| `IPFS_GATEWAY_URL` | IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID | URL template | Y | | -| `QUEUE_HIGH_WATER` | Max number of jobs allowed on the '' before blockchain scan will be paused to allow queue to drain | >= 100 | | 1000 | -| `REDIS_URL` | Connection URL for Redis | URL | Y | -| `STARTING_BLOCK` | Block number from which the service will start scanning the chain | > 0 | | 1 | -| `WEBHOOK_FAILURE_THRESHOLD` | Number of failures allowing in the provider webhook before the service is marked down | > 0 | | 3 | -| `WEBHOOK_RETRY_INTERVAL_SECONDS` | Number of seconds between provider webhook retry attempts when failing | > 0 | | 10 | +| Name | Description | Range/Type | Required? | Default | +| ---------------------------------- | --------------------------------------------------------------------------------------------------------------- | :--------------------: | :-------: | :--------------: | +| `API_PORT` | HTTP port that the application listens on | 1025 - 65535 | | 3000 | +| `BLOCKCHAIN_SCAN_INTERVAL_SECONDS` | How many seconds to delay between successive scans of the chain for new content (after end of chain is reached) | > 0 | | 12 | +| `CACHE_KEY_PREFIX` | Prefix to use for Redis cache keys | string | | content-watcher: | +| `FREQUENCY_URL` | Blockchain node address | http(s): or ws(s): URL | Y | | +| `IPFS_BASIC_AUTH_SECRET` | If required for read requests, put Infura auth token here, or leave blank for default Kubo RPC | string | N | blank | +| `IPFS_BASIC_AUTH_USER` | If required for read requests, put Infura Project ID here, or leave blank for default Kubo RPC | string | N | blank | +| `IPFS_ENDPOINT` | URL to IPFS endpoint | URL | Y | | +| `IPFS_GATEWAY_URL` | IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID | URL template | Y | | +| `QUEUE_HIGH_WATER` | Max number of jobs allowed on the '' before blockchain scan will be paused to allow queue to drain | >= 100 | | 1000 | +| `REDIS_URL` | Connection URL for Redis | URL | Y | +| `STARTING_BLOCK` | Block number from which the service will start scanning the chain | > 0 | | 1 | +| `WEBHOOK_FAILURE_THRESHOLD` | Number of failures allowing in the provider webhook before the service is marked down | > 0 | | 3 | +| `WEBHOOK_RETRY_INTERVAL_SECONDS` | Number of seconds between provider webhook retry attempts when failing | > 0 | | 10 | diff --git a/services/content-watcher/apps/api/src/api.module.ts b/services/content-watcher/apps/api/src/api.module.ts index c8a16566..59633737 100644 --- a/services/content-watcher/apps/api/src/api.module.ts +++ b/services/content-watcher/apps/api/src/api.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; import { EventEmitterModule } from '@nestjs/event-emitter'; -import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; import { RedisModule } from '@songkeys/nestjs-redis'; import { BullBoardModule } from '@bull-board/nestjs'; @@ -13,13 +12,15 @@ import { CrawlerModule } from '@libs/common/crawler/crawler.module'; import { IPFSProcessorModule } from '@libs/common/ipfs/ipfs.module'; import { PubSubModule } from '@libs/common/pubsub/pubsub.module'; import { ScannerModule } from '@libs/common/scanner/scanner.module'; -import { ConfigModule } from '@libs/common/config/config.module'; -import { ConfigService } from '@libs/common/config/config.service'; +import { AppConfigModule } from '@libs/common/config/config.module'; +import { AppConfigService } from '@libs/common/config/config.service'; import * as QueueConstants from '@libs/common'; +import { QueueModule } from '@libs/common/queues/queue.module'; @Module({ imports: [ - ConfigModule, + AppConfigModule, + ScheduleModule.forRoot(), BlockchainModule, ScannerModule, CrawlerModule, @@ -27,117 +28,55 @@ import * as QueueConstants from '@libs/common'; PubSubModule, RedisModule.forRootAsync( { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], + // imports: [ConfigModule], + useFactory: (configService: AppConfigService) => ({ + config: [{ url: configService.redisUrl.toString(), keyPrefix: configService.cacheKeyPrefix }], }), - inject: [ConfigService], + inject: [AppConfigService], }, true, // isGlobal ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], + QueueModule, + + // Bullboard UI + BullBoardModule.forRoot({ + route: '/queues', + adapter: ExpressAdapter, }), - BullModule.registerQueue( + BullBoardModule.forFeature( { name: QueueConstants.REQUEST_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, + adapter: BullMQAdapter, }, { name: QueueConstants.IPFS_QUEUE, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, + adapter: BullMQAdapter, }, { name: QueueConstants.BROADCAST_QUEUE_NAME, + adapter: BullMQAdapter, }, { name: QueueConstants.REPLY_QUEUE_NAME, + adapter: BullMQAdapter, }, { name: QueueConstants.REACTION_QUEUE_NAME, + adapter: BullMQAdapter, }, { name: QueueConstants.TOMBSTONE_QUEUE_NAME, + adapter: BullMQAdapter, }, { name: QueueConstants.PROFILE_QUEUE_NAME, + adapter: BullMQAdapter, }, { name: QueueConstants.UPDATE_QUEUE_NAME, + adapter: BullMQAdapter, }, ), - - // Bullboard UI - BullBoardModule.forRoot({ - route: '/queues', - adapter: ExpressAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.REQUEST_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.IPFS_QUEUE, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.BROADCAST_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.REPLY_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.REACTION_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.TOMBSTONE_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.PROFILE_QUEUE_NAME, - adapter: BullMQAdapter, - }), - BullBoardModule.forFeature({ - name: QueueConstants.UPDATE_QUEUE_NAME, - adapter: BullMQAdapter, - }), EventEmitterModule.forRoot({ // Use this instance throughout the application global: true, @@ -156,12 +95,11 @@ import * as QueueConstants from '@libs/common'; // disable throwing uncaughtException if an error event is emitted and it has no listeners ignoreErrors: false, }), - ScheduleModule.forRoot(), ], providers: [ApiService], // Controller order determines the order of display for docs // v[Desc first][ABC Second], Health, and then Dev only last controllers: [ScanControllerV1, SearchControllerV1, WebhookControllerV1, HealthController], - exports: [], + exports: [RedisModule, ScheduleModule], }) export class ApiModule {} diff --git a/services/content-watcher/apps/api/src/api.service.ts b/services/content-watcher/apps/api/src/api.service.ts index a41993f0..be8d2c23 100644 --- a/services/content-watcher/apps/api/src/api.service.ts +++ b/services/content-watcher/apps/api/src/api.service.ts @@ -4,12 +4,13 @@ import Redis from 'ioredis'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { ContentSearchRequestDto, REQUEST_QUEUE_NAME, calculateJobId } from '../../../libs/common/src'; -import { ScannerService } from '../../../libs/common/src/scanner/scanner'; +import { ScannerService } from '../../../libs/common/src/scanner/scanner.service'; import { EVENTS_TO_WATCH_KEY, REGISTERED_WEBHOOK_KEY } from '../../../libs/common/src/constants'; import { ChainWatchOptionsDto } from '../../../libs/common/src/dtos/chain.watch.dto'; -import { WebhookRegistrationDto } from '../../../libs/common/src/dtos/subscription.webhook.dto'; +import { IWebhookRegistration } from '../../../libs/common/src/dtos/subscription.webhook.dto'; import * as RedisUtils from '../../../libs/common/src/utils/redis'; import { IScanReset } from '../../../libs/common/src/interfaces/scan-reset.interface'; +import { IAnnouncementSubscription } from '@libs/common/interfaces/announcement-subscription.interface'; @Injectable() export class ApiService { @@ -50,7 +51,7 @@ export class ApiService { } public async searchContent(contentSearchRequestDto: ContentSearchRequestDto) { - const jobId = contentSearchRequestDto.id ?? calculateJobId(contentSearchRequestDto); + const jobId = contentSearchRequestDto.clientReferenceId ?? calculateJobId(contentSearchRequestDto); this.logger.debug(`Searching for content with request ${JSON.stringify(contentSearchRequestDto)}`); const job = await this.requestQueue.getJob(jobId); @@ -60,18 +61,18 @@ export class ApiService { } this.requestQueue.remove(jobId); // eslint-disable-next-line no-param-reassign - contentSearchRequestDto.id = jobId; + contentSearchRequestDto.clientReferenceId = jobId; const jobPromise = await this.requestQueue.add(`Content Search ${jobId}`, contentSearchRequestDto, { jobId }); const JOB_REQUEST_WATCH_KEY = `${EVENTS_TO_WATCH_KEY}:${jobId}`; await this.redis.setex(JOB_REQUEST_WATCH_KEY, RedisUtils.STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, JSON.stringify(contentSearchRequestDto.filters)); return jobPromise; } - public async setWebhook(webhookRegistration: WebhookRegistrationDto) { + public async setWebhook(webhookRegistration: IWebhookRegistration) { this.logger.debug(`Registering webhook ${JSON.stringify(webhookRegistration)}`); const currentRegistedWebooks = await this.redis.get(REGISTERED_WEBHOOK_KEY); - let currentWebhookRegistrationDtos: { announcementType: string; urls: string[] }[] = []; + let currentWebhookRegistrationDtos: IAnnouncementSubscription[] = []; if (currentRegistedWebooks) { currentWebhookRegistrationDtos = JSON.parse(currentRegistedWebooks); } @@ -97,7 +98,7 @@ export class ApiService { await this.redis.del(REGISTERED_WEBHOOK_KEY); } - public async getRegisteredWebhooks(): Promise { + public async getRegisteredWebhooks(): Promise { this.logger.debug('Getting registered webhooks'); const registeredWebhooks = await this.redis.get(REGISTERED_WEBHOOK_KEY); return registeredWebhooks ? JSON.parse(registeredWebhooks) : []; diff --git a/services/content-watcher/apps/api/src/main.ts b/services/content-watcher/apps/api/src/main.ts index cc6d44fa..e8a99f6d 100644 --- a/services/content-watcher/apps/api/src/main.ts +++ b/services/content-watcher/apps/api/src/main.ts @@ -3,7 +3,7 @@ import { Logger, ValidationPipe } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { ApiModule } from './api.module'; import { initSwagger } from '../../../libs/common/src/config/swagger_config'; -import { ConfigService } from '../../../libs/common/src/config/config.service'; +import { AppConfigService } from '../../../libs/common/src/config/config.service'; const logger = new Logger('main'); @@ -17,7 +17,7 @@ async function bootstrap() { const app = await NestFactory.create(ApiModule, { logger: process.env.DEBUG ? ['error', 'warn', 'log', 'verbose', 'debug'] : ['error', 'warn', 'log'], }); - const configService = app.get(ConfigService); + const configService = app.get(AppConfigService); // Get event emitter & register a shutdown listener const eventEmitter = app.get(EventEmitter2); diff --git a/services/content-watcher/apps/api/src/metadata.ts b/services/content-watcher/apps/api/src/metadata.ts index 8d74e832..ba8662bd 100644 --- a/services/content-watcher/apps/api/src/metadata.ts +++ b/services/content-watcher/apps/api/src/metadata.ts @@ -98,13 +98,14 @@ export default async () => { { ChainWatchOptionsDto: { schemaIds: { required: true, type: () => [Number] }, dsnpIds: { required: true, type: () => [String] } } }, ], [ - import('../../../libs/common/src/dtos/request-job.dto'), + import('../../../libs/common/src/dtos/content-search-request.dto'), { ContentSearchRequestDto: { - id: { required: true, type: () => String }, + clientReferenceId: { required: true, type: () => String }, startBlock: { required: true, type: () => Number, minimum: 1 }, - endBlock: { required: true, type: () => Number, minimum: 1 }, + blockCount: { required: true, type: () => Number, minimum: 1 }, filters: { required: true, type: () => t['../../../libs/common/src/dtos/chain.watch.dto'].ChainWatchOptionsDto }, + webhookUrl: { required: true, type: () => String }, }, }, ], diff --git a/services/content-watcher/dev.Dockerfile b/services/content-watcher/dev.Dockerfile index 9cfdcebe..cb942c5b 100644 --- a/services/content-watcher/dev.Dockerfile +++ b/services/content-watcher/dev.Dockerfile @@ -19,4 +19,4 @@ USER node EXPOSE 3000 # Run the application. -ENTRYPOINT [ "npm", "run", "start:api:watch" ] +ENTRYPOINT [ "npm", "run", "start:watch" ] diff --git a/services/content-watcher/docker-compose.yaml b/services/content-watcher/docker-compose.yaml index 9ca71688..dc272b92 100644 --- a/services/content-watcher/docker-compose.yaml +++ b/services/content-watcher/docker-compose.yaml @@ -96,7 +96,7 @@ services: tags: - content-watcher-service:latest environment: - << : *content-watcher-environment + <<: *content-watcher-environment ports: - 3000:3000 volumes: diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index e6f596c6..46df5cbd 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -17,9 +17,6 @@ IPFS_GATEWAY_URL="http://127.0.0.1:8080/ipfs/[CID]" # Blockchain node address FREQUENCY_URL=ws://0.0.0.0:9944 -# Block number from which the service will start scanning the chain -STARTING_BLOCK=1 - # Redis URL REDIS_URL=redis://0.0.0.0:6379 @@ -39,3 +36,6 @@ WEBHOOK_RETRY_INTERVAL_SECONDS=10 # Port that the application REST endpoints listen on API_PORT=3000 + +# Prefix to use for Redis cache keys +CACHE_KEY_PREFIX=content-watcher: diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts index 94d62a67..a4d1ac21 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.module.ts @@ -1,14 +1,9 @@ -/* -https://docs.nestjs.com/modules -*/ - import { Module } from '@nestjs/common'; import { BlockchainService } from './blockchain.service'; -import { ConfigModule } from '../config/config.module'; import { ChainEventProcessorService } from './chain-event-processor.service'; @Module({ - imports: [ConfigModule], + imports: [], controllers: [], providers: [BlockchainService, ChainEventProcessorService], exports: [BlockchainService, ChainEventProcessorService], diff --git a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts index c205c555..8af76170 100644 --- a/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts +++ b/services/content-watcher/libs/common/src/blockchain/blockchain.service.ts @@ -7,7 +7,7 @@ import { KeyringPair } from '@polkadot/keyring/types'; import { BlockHash, BlockNumber, SignedBlock } from '@polkadot/types/interfaces'; import { SubmittableExtrinsic } from '@polkadot/api/types'; import { AnyNumber, ISubmittableResult } from '@polkadot/types/types'; -import { ConfigService } from '../config/config.service'; +import { AppConfigService } from '../config/config.service'; import { Extrinsic } from './extrinsic'; @Injectable() @@ -16,7 +16,7 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS public apiPromise: ApiPromise; - private configService: ConfigService; + private configService: AppConfigService; private logger: Logger; @@ -54,7 +54,7 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS await Promise.all(promises); } - constructor(configService: ConfigService) { + constructor(configService: AppConfigService) { this.configService = configService; this.logger = new Logger(this.constructor.name); } @@ -63,7 +63,7 @@ export class BlockchainService implements OnApplicationBootstrap, OnApplicationS return firstValueFrom(this.api.rpc.chain.getBlockHash(block)); } - public getBlock(block: BlockHash): Promise { + public getBlock(block?: BlockHash): Promise { return firstValueFrom(this.api.rpc.chain.getBlock(block)); } diff --git a/services/content-watcher/libs/common/src/config/config.module.ts b/services/content-watcher/libs/common/src/config/config.module.ts index 0b28661f..8b92d55e 100644 --- a/services/content-watcher/libs/common/src/config/config.module.ts +++ b/services/content-watcher/libs/common/src/config/config.module.ts @@ -1,12 +1,13 @@ -import { ConfigModule as NestConfigModule } from '@nestjs/config'; -import { Module } from '@nestjs/common'; -import { ConfigService } from './config.service'; +import { ConfigModule } from '@nestjs/config'; +import { Global, Module } from '@nestjs/common'; +import { AppConfigService } from './config.service'; import { configModuleOptions } from './env.config'; +@Global() @Module({ - imports: [NestConfigModule.forRoot(configModuleOptions)], + imports: [ConfigModule.forRoot(configModuleOptions)], controllers: [], - providers: [ConfigService], - exports: [ConfigService], + providers: [AppConfigService], + exports: [AppConfigService], }) -export class ConfigModule {} +export class AppConfigModule {} diff --git a/services/content-watcher/libs/common/src/config/config.service.spec.ts b/services/content-watcher/libs/common/src/config/config.service.spec.ts index abec7d55..755da7a6 100644 --- a/services/content-watcher/libs/common/src/config/config.service.spec.ts +++ b/services/content-watcher/libs/common/src/config/config.service.spec.ts @@ -1,10 +1,10 @@ import { Test } from '@nestjs/testing'; import { describe, it, expect, beforeAll, jest } from '@jest/globals'; import { ConfigModule } from '@nestjs/config'; -import { ConfigService } from './config.service'; +import { AppConfigService } from './config.service'; import { configModuleOptions } from './env.config'; -const setupConfigService = async (envObj: any): Promise => { +const setupConfigService = async (envObj: any): Promise => { jest.resetModules(); Object.keys(process.env).forEach((key) => { @@ -23,19 +23,18 @@ const setupConfigService = async (envObj: any): Promise => { }), ], controllers: [], - providers: [ConfigService], + providers: [AppConfigService], }).compile(); await ConfigModule.envVariablesLoaded; - return moduleRef.get(ConfigService); + return moduleRef.get(AppConfigService); }; describe('ContentWatcherConfigService', () => { const ALL_ENV: Record = { REDIS_URL: undefined, FREQUENCY_URL: undefined, - STARTING_BLOCK: undefined, IPFS_ENDPOINT: undefined, IPFS_GATEWAY_URL: undefined, IPFS_BASIC_AUTH_USER: undefined, @@ -45,6 +44,7 @@ describe('ContentWatcherConfigService', () => { WEBHOOK_FAILURE_THRESHOLD: undefined, WEBHOOK_RETRY_INTERVAL_SECONDS: undefined, API_PORT: undefined, + CACHE_KEY_PREFIX: undefined, }; beforeAll(() => { @@ -95,7 +95,7 @@ describe('ContentWatcherConfigService', () => { }); describe('valid environment', () => { - let contentWatcherConfigService: ConfigService; + let contentWatcherConfigService: AppConfigService; beforeAll(async () => { contentWatcherConfigService = await setupConfigService(ALL_ENV); }); @@ -123,5 +123,9 @@ describe('ContentWatcherConfigService', () => { it('should get api port', () => { expect(contentWatcherConfigService.apiPort).toStrictEqual(parseInt(ALL_ENV.API_PORT as string, 10)); }); + + it('should get cache key prefix', () => { + expect(contentWatcherConfigService.cacheKeyPrefix).toStrictEqual(ALL_ENV.CACHE_KEY_PREFIX?.toString()); + }); }); }); diff --git a/services/content-watcher/libs/common/src/config/config.service.ts b/services/content-watcher/libs/common/src/config/config.service.ts index ade4aa83..850c01c5 100644 --- a/services/content-watcher/libs/common/src/config/config.service.ts +++ b/services/content-watcher/libs/common/src/config/config.service.ts @@ -1,10 +1,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* -https://docs.nestjs.com/providers#services -*/ - import { Injectable, Logger } from '@nestjs/common'; -import { ConfigService as NestConfigService } from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; export interface ConfigEnvironmentVariables { IPFS_ENDPOINT: URL; @@ -13,20 +9,20 @@ export interface ConfigEnvironmentVariables { IPFS_BASIC_AUTH_SECRET: string; REDIS_URL: URL; FREQUENCY_URL: URL; - STARTING_BLOCK: string; BLOCKCHAIN_SCAN_INTERVAL_SECONDS: number; QUEUE_HIGH_WATER: number; WEBHOOK_FAILURE_THRESHOLD: number; WEBHOOK_RETRY_INTERVAL_SECONDS: number; API_PORT: number; + CACHE_KEY_PREFIX: string; } /// Config service to get global app and provider-specific config values. @Injectable() -export class ConfigService { +export class AppConfigService { private logger: Logger; - constructor(private nestConfigService: NestConfigService) { + constructor(private nestConfigService: ConfigService) { this.logger = new Logger(this.constructor.name); } @@ -34,12 +30,12 @@ export class ConfigService { return this.nestConfigService.get('REDIS_URL')!; } - public get frequencyUrl(): URL { - return this.nestConfigService.get('FREQUENCY_URL')!; + public get cacheKeyPrefix(): string { + return this.nestConfigService.get('CACHE_KEY_PREFIX')!; } - public get startingBlock(): number | undefined { - return this.nestConfigService.get('STARTING_BLOCK')!; + public get frequencyUrl(): URL { + return this.nestConfigService.get('FREQUENCY_URL')!; } public get blockchainScanIntervalSeconds(): number { diff --git a/services/content-watcher/libs/common/src/config/env.config.ts b/services/content-watcher/libs/common/src/config/env.config.ts index b158bf25..1cbdacc1 100644 --- a/services/content-watcher/libs/common/src/config/env.config.ts +++ b/services/content-watcher/libs/common/src/config/env.config.ts @@ -10,13 +10,11 @@ export const configModuleOptions: ConfigModuleOptions = { IPFS_BASIC_AUTH_SECRET: Joi.string().allow('').default(''), REDIS_URL: Joi.string().uri().required(), FREQUENCY_URL: Joi.string().uri().required(), - STARTING_BLOCK: Joi.number().min(1), - BLOCKCHAIN_SCAN_INTERVAL_SECONDS: Joi.number() - .min(1) - .default(12), + BLOCKCHAIN_SCAN_INTERVAL_SECONDS: Joi.number().min(1).default(12), QUEUE_HIGH_WATER: Joi.number().min(100).default(1000), WEBHOOK_FAILURE_THRESHOLD: Joi.number().min(0).default(3), WEBHOOK_RETRY_INTERVAL_SECONDS: Joi.number().min(1).default(10), API_PORT: Joi.number().min(0).default(3000), + CACHE_KEY_PREFIX: Joi.string().default('content-watcher:'), }), }; diff --git a/services/content-watcher/libs/common/src/crawler/crawler.module.ts b/services/content-watcher/libs/common/src/crawler/crawler.module.ts index 8a681dcd..b2e0d0f6 100644 --- a/services/content-watcher/libs/common/src/crawler/crawler.module.ts +++ b/services/content-watcher/libs/common/src/crawler/crawler.module.ts @@ -1,67 +1,10 @@ -/* -https://docs.nestjs.com/modules -*/ - import { Module } from '@nestjs/common'; -import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; -import { ConfigModule } from '../config/config.module'; -import { CrawlerService } from './crawler'; +import { CrawlerService } from './crawler.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; -import { ConfigService } from '../config/config.service'; -import * as QueueConstants from '../utils/queues'; @Module({ - imports: [ - ConfigModule, - BlockchainModule, - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue({ - name: QueueConstants.IPFS_QUEUE, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }), - BullModule.registerQueue({ - name: QueueConstants.REQUEST_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }), - ScheduleModule.forRoot(), - ], + imports: [BlockchainModule, ScheduleModule], controllers: [], providers: [CrawlerService], exports: [CrawlerService], diff --git a/services/content-watcher/libs/common/src/crawler/crawler.service.ts b/services/content-watcher/libs/common/src/crawler/crawler.service.ts new file mode 100644 index 00000000..6bec8225 --- /dev/null +++ b/services/content-watcher/libs/common/src/crawler/crawler.service.ts @@ -0,0 +1,78 @@ +/* eslint-disable no-underscore-dangle */ +import { Injectable } from '@nestjs/common'; +import { InjectQueue, Processor } from '@nestjs/bullmq'; +import Redis from 'ioredis'; +import { InjectRedis } from '@songkeys/nestjs-redis'; +import { DelayedError, Job, Queue } from 'bullmq'; +import * as QueueConstants from '../queues/queue-constants'; +import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; +import { BaseConsumer } from '../utils/base-consumer'; +import { ContentSearchRequestDto } from '../dtos/content-search-request.dto'; +import { ChainEventProcessorService } from '../blockchain/chain-event-processor.service'; +import { BlockchainService } from '../blockchain/blockchain.service'; + +const CRAWLER_BLOCK_CHUNK_SIZE = 500; + +@Injectable() +@Processor(QueueConstants.REQUEST_QUEUE_NAME, { + concurrency: 2, +}) +export class CrawlerService extends BaseConsumer { + constructor( + @InjectRedis() private readonly cache: Redis, + @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, + private readonly chainEventService: ChainEventProcessorService, + private readonly blockchainService: BlockchainService, + ) { + super(); + } + + async process(job: Job): Promise { + this.logger.log(`Processing crawler job ${job.id}: ${JSON.stringify(job.data)}`); + + try { + let startBlock = job.data.startBlock; + if (!startBlock) { + startBlock = await this.blockchainService.getLatestFinalizedBlockNumber(); + job.data.startBlock = startBlock; + this.logger.debug(`No starting block specified; starting from end of chain at block ${startBlock}`); + } + let blockList = new Array(job.data.blockCount).fill(0).map((_v, index) => startBlock - index); + blockList.reverse(); + + // Process block list in chunks so as not to overload the async queue + await this.processBlockList(job.data.clientReferenceId, blockList.slice(0, CRAWLER_BLOCK_CHUNK_SIZE), job.data.filters); + blockList = blockList.slice(CRAWLER_BLOCK_CHUNK_SIZE); + + if (blockList.length > 0) { + job.data.blockCount = blockList.length; + await job.updateData(job.data); + await job.moveToDelayed(Date.now()); + throw new DelayedError(); + } + + this.logger.log(`Finished processing job ${job.id}`); + } catch (error) { + if (error instanceof DelayedError) { + throw error; + } + + this.logger.error(`Error processing crawler search job: ${JSON.stringify(error)}`); + throw error; + } + } + + private async processBlockList(clientReferenceId: string, blockList: number[], filters: ChainWatchOptionsDto) { + this.logger.debug(`Processing block list ${Math.min(...blockList)}...${Math.max(...blockList)}`); + await Promise.all( + blockList.map(async (blockNumber) => { + const messages = await this.chainEventService.getMessagesInBlock(blockNumber, filters); + if (messages.length > 0) { + this.logger.debug(`Found ${messages.length} messages for block ${blockNumber}`); + } + // eslint-disable-next-line no-await-in-loop + await this.chainEventService.queueIPFSJobs(messages, this.ipfsQueue, clientReferenceId); + }), + ); + } +} diff --git a/services/content-watcher/libs/common/src/crawler/crawler.ts b/services/content-watcher/libs/common/src/crawler/crawler.ts deleted file mode 100644 index 4901a5ff..00000000 --- a/services/content-watcher/libs/common/src/crawler/crawler.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-disable no-underscore-dangle */ -import { Injectable } from '@nestjs/common'; -import { InjectQueue, Processor } from '@nestjs/bullmq'; -import Redis from 'ioredis'; -import { InjectRedis } from '@songkeys/nestjs-redis'; -import { Job, Queue } from 'bullmq'; -import * as QueueConstants from '../utils/queues'; -import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; -import { BaseConsumer } from '../utils/base-consumer'; -import { ContentSearchRequestDto } from '../dtos/request-job.dto'; -import { REGISTERED_WEBHOOK_KEY } from '../constants'; -import { ChainEventProcessorService } from '../blockchain/chain-event-processor.service'; - -@Injectable() -@Processor(QueueConstants.REQUEST_QUEUE_NAME, { - concurrency: 2, -}) -export class CrawlerService extends BaseConsumer { - constructor( - @InjectRedis() private readonly cache: Redis, - @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, - private readonly chainEventService: ChainEventProcessorService, - ) { - super(); - } - - async process(job: Job): Promise { - this.logger.log(`Processing crawler job ${job.id}`); - const registeredWebhook = await this.cache.get(REGISTERED_WEBHOOK_KEY); - - if (!registeredWebhook) { - throw new Error('No registered webhook to send data to'); - } - const blockList: number[] = []; - for (let i = job.data.startBlock; i <= job.data.endBlock; i += 1) { - blockList.push(i); - } - await this.processBlockList(job.data.id, blockList, job.data.filters); - - this.logger.log(`Finished processing job ${job.id}`); - } - - private async processBlockList(id: string, blockList: number[], filters: ChainWatchOptionsDto) { - await Promise.all( - blockList.map(async (blockNumber) => { - const messages = await this.chainEventService.getMessagesInBlock(blockNumber, filters); - if (messages.length > 0) { - this.logger.debug(`Found ${messages.length} messages for block ${blockNumber}`); - } - // eslint-disable-next-line no-await-in-loop - await this.chainEventService.queueIPFSJobs(messages, this.ipfsQueue, id); - }), - ); - } -} diff --git a/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts index 900a9da0..92f2007b 100644 --- a/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/chain.watch.dto.ts @@ -19,6 +19,7 @@ export class ChainWatchOptionsDto { type: 'number', }, description: 'Specific schema ids to watch for', + required: false, example: [1, 19], }) schemaIds: number[]; @@ -29,6 +30,7 @@ export class ChainWatchOptionsDto { @Type(() => String) @ApiProperty({ description: 'Specific dsnpIds (msa_id) to watch for', + required: false, example: ['10074', '100001'], }) dsnpIds: string[]; diff --git a/services/content-watcher/libs/common/src/dtos/content-search-request.dto.ts b/services/content-watcher/libs/common/src/dtos/content-search-request.dto.ts new file mode 100644 index 00000000..bab45408 --- /dev/null +++ b/services/content-watcher/libs/common/src/dtos/content-search-request.dto.ts @@ -0,0 +1,46 @@ +import { IsInt, IsOptional, IsPositive, IsString, IsUrl } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { ChainWatchOptionsDto } from './chain.watch.dto'; + +export class ContentSearchRequestDto { + @IsOptional() + @IsString() + @ApiProperty({ + description: 'An optional client-supplied reference ID by which it can identify the result of this search', + required: false, + }) + clientReferenceId: string; + + @IsOptional() + @IsInt() + @IsPositive() + @ApiProperty({ + description: 'The block number to search (backward) from', + required: false, + example: 100, + }) + startBlock: number; + + @IsInt() + @IsPositive() + @ApiProperty({ + description: 'The number of blocks to scan (backwards)', + required: true, + example: 101, + }) + blockCount: number; + + @IsOptional() + @ApiProperty({ + description: 'The schemaIds/dsnpIds to filter by', + required: false, + }) + filters: ChainWatchOptionsDto; + + @IsUrl({ require_tld: false }) + @ApiProperty({ + description: 'A webhook URL to be notified of the results of this search', + required: true, + }) + webhookUrl: string; +} diff --git a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts b/services/content-watcher/libs/common/src/dtos/request-job.dto.ts deleted file mode 100644 index b950ad19..00000000 --- a/services/content-watcher/libs/common/src/dtos/request-job.dto.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { IsInt, IsOptional, IsPositive, IsString } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; -import { ChainWatchOptionsDto } from './chain.watch.dto'; - -export class ContentSearchRequestDto { - @IsOptional() - @IsString() - id: string; - - @IsInt() - @IsPositive() - @ApiProperty({ - description: 'The starting block number to search from', - example: 100, - }) - startBlock: number; - - @IsInt() - @IsPositive() - @ApiProperty({ - description: 'The ending block number to search to', - example: 101, - }) - endBlock: number; - - @IsOptional() - @ApiProperty({ - description: 'The schemaIds/dsnpIds to filter by', - }) - filters: ChainWatchOptionsDto; -} diff --git a/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts b/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts index 0a351559..8c0c05e6 100644 --- a/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts +++ b/services/content-watcher/libs/common/src/dtos/subscription.webhook.dto.ts @@ -1,8 +1,12 @@ import { IsArray, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; -// WebhookRegistrationDto.ts -export class WebhookRegistrationDto { +export interface IWebhookRegistration { + url: string; + announcementTypes: string[]; +} + +export class WebhookRegistrationDto implements IWebhookRegistration { @IsString() @ApiProperty({ description: 'Webhook URL', diff --git a/services/content-watcher/libs/common/src/index.ts b/services/content-watcher/libs/common/src/index.ts index 9c415d2c..e64aa1ea 100644 --- a/services/content-watcher/libs/common/src/index.ts +++ b/services/content-watcher/libs/common/src/index.ts @@ -2,5 +2,5 @@ export * from './dtos/announcement.dto'; export * from './dtos/activity.dto'; export * from './dtos/common.dto'; export * from './dtos/validation.dto'; -export * from './dtos/request-job.dto'; -export * from './utils/queues'; +export * from './dtos/content-search-request.dto'; +export * from './queues/queue-constants'; diff --git a/services/content-watcher/libs/common/src/interfaces/announcement-subscription.interface.ts b/services/content-watcher/libs/common/src/interfaces/announcement-subscription.interface.ts new file mode 100644 index 00000000..b7273912 --- /dev/null +++ b/services/content-watcher/libs/common/src/interfaces/announcement-subscription.interface.ts @@ -0,0 +1,4 @@ +export interface IAnnouncementSubscription { + announcementType: string; + urls: string[]; +} diff --git a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts index 9df9b367..161d36a4 100644 --- a/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts +++ b/services/content-watcher/libs/common/src/interfaces/ipfs.job.interface.ts @@ -6,6 +6,7 @@ export interface IIPFSJob { blockNumber: number; index: number; requestId?: string; + webhookUrl?: string; } export function createIPFSQueueJob( @@ -16,9 +17,10 @@ export function createIPFSQueueJob( cid: string, index: number, requestId?: string, + webhookUrl?: string, ): { key: string; data: IIPFSJob } { return { - key: `${msaId}:${providerId}:${index}:${requestId}`, + key: `${msaId}:${providerId}:${blockNumber}:${index}:${requestId}`, data: { msaId, providerId, @@ -27,6 +29,7 @@ export function createIPFSQueueJob( blockNumber, index, requestId, + webhookUrl, }, }; } diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts index 9fe4c159..6b2f4987 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.dsnp.ts @@ -1,12 +1,12 @@ import { Injectable, Logger } from '@nestjs/common'; -import { Job, Queue } from 'bullmq'; +import { Job, JobsOptions, Queue } from 'bullmq'; import { InjectQueue, Processor } from '@nestjs/bullmq'; import { hexToString } from '@polkadot/util'; import parquet from '@dsnp/parquetjs'; import { bases } from 'multiformats/basics'; -import { ConfigService } from '../config/config.service'; +import { AppConfigService } from '../config/config.service'; import { calculateJobId } from '..'; -import * as QueueConstants from '../utils/queues'; +import * as QueueConstants from '../queues/queue-constants'; import { IIPFSJob } from '../interfaces/ipfs.job.interface'; import { BaseConsumer } from '../utils/base-consumer'; import { IpfsService } from '../utils/ipfs.client'; @@ -20,6 +20,7 @@ import { TombstoneAnnouncement, UpdateAnnouncement, } from '../types/content-announcement'; +import { isBroadcast, isProfile, isReaction, isReply, isTombstone, isTypedAnnouncement, isUpdate } from '../utils/type-guards'; @Injectable() @Processor(QueueConstants.IPFS_QUEUE, { @@ -35,7 +36,7 @@ export class IPFSContentProcessor extends BaseConsumer { @InjectQueue(QueueConstants.REPLY_QUEUE_NAME) private replyQueue: Queue, @InjectQueue(QueueConstants.PROFILE_QUEUE_NAME) private profileQueue: Queue, @InjectQueue(QueueConstants.UPDATE_QUEUE_NAME) private updateQueue: Queue, - private configService: ConfigService, + private configService: AppConfigService, private ipfsService: IpfsService, ) { super(); @@ -75,132 +76,87 @@ export class IPFSContentProcessor extends BaseConsumer { } } + private async enqueueAnnouncementResponse(announcementResponse: AnnouncementResponse, name: string, queue: Queue): Promise { + if (!(await this.isQueueFull(queue))) { + const jobId = calculateJobId(announcementResponse); + await queue.add(name, announcementResponse, { jobId }); + } + } + private async buildAndQueueDSNPAnnouncements(records: any[], jobData: IIPFSJob): Promise { - const jobRequestId = jobData.requestId; - const blockNumer = jobData.blockNumber; + const { blockNumber, requestId, schemaId, webhookUrl } = jobData; records.forEach(async (mapRecord) => { - switch (mapRecord.announcementType) { - case AnnouncementType.Broadcast: { - const recordAnnouncement = mapRecord as BroadcastAnnouncement; - const broadCastResponse: AnnouncementResponse = { - schemaId: jobData.schemaId, - blockNumber: blockNumer, - announcement: { - fromId: recordAnnouncement.fromId, - contentHash: bases.base58btc.encode(recordAnnouncement.contentHash as any), - url: recordAnnouncement.url, - announcementType: recordAnnouncement.announcementType, - }, - requestId: jobRequestId, - }; - if (!(await this.isQueueFull(this.broadcastQueue))) { - const jobId = calculateJobId(broadCastResponse); - await this.broadcastQueue.add('Broadcast', broadCastResponse, { jobId }); - } - break; - } - case AnnouncementType.Tombstone: { - const tombRecord = mapRecord as TombstoneAnnouncement; - const tombstoneResponse: AnnouncementResponse = { - schemaId: jobData.schemaId, - blockNumber: blockNumer, - announcement: { - fromId: tombRecord.fromId, - targetAnnouncementType: tombRecord.targetAnnouncementType, - targetContentHash: bases.base58btc.encode(tombRecord.targetContentHash as any), - announcementType: tombRecord.announcementType, - }, - requestId: jobRequestId, - }; - if (!(await this.isQueueFull(this.tombstoneQueue))) { - const jobId = calculateJobId(tombstoneResponse); - await this.tombstoneQueue.add('Tombstone', tombstoneResponse, { jobId }); - } - break; - } - case AnnouncementType.Reaction: { - const reactionRecord = mapRecord as ReactionAnnouncement; - const reactionResponse: AnnouncementResponse = { - schemaId: jobData.schemaId, - blockNumber: blockNumer, - announcement: { - fromId: reactionRecord.fromId, - announcementType: reactionRecord.announcementType, - inReplyTo: reactionRecord.inReplyTo, - emoji: reactionRecord.emoji, - apply: reactionRecord.apply, - }, - requestId: jobRequestId, - }; - if (!(await this.isQueueFull(this.reactionQueue))) { - const jobId = calculateJobId(reactionResponse); - await this.reactionQueue.add('Reaction', reactionResponse, { jobId }); - } - break; - } - case AnnouncementType.Reply: { - const replyRecord = mapRecord as ReplyAnnouncement; - const replyResponse: AnnouncementResponse = { - schemaId: jobData.schemaId, - blockNumber: blockNumer, - announcement: { - fromId: replyRecord.fromId, - announcementType: replyRecord.announcementType, - url: replyRecord.url, - inReplyTo: replyRecord.inReplyTo, - contentHash: bases.base58btc.encode(replyRecord.contentHash as any), - }, - requestId: jobRequestId, - }; - if (!(await this.isQueueFull(this.replyQueue))) { - const jobId = calculateJobId(replyResponse); - await this.replyQueue.add('Reply', replyResponse, { jobId }); - } - break; - } - case AnnouncementType.Profile: { - const profileRecord = mapRecord as ProfileAnnouncement; - const profileResponse: AnnouncementResponse = { - schemaId: jobData.schemaId, - blockNumber: blockNumer, - announcement: { - fromId: profileRecord.fromId, - announcementType: profileRecord.announcementType, - url: profileRecord.url, - contentHash: bases.base58btc.encode(profileRecord.contentHash as any), - }, - requestId: jobRequestId, - }; - if (!(await this.isQueueFull(this.profileQueue))) { - const jobId = calculateJobId(profileResponse); - this.profileQueue.add('Profile', profileResponse, { jobId }); - } - break; - } - case AnnouncementType.Update: { - const updateRecord = mapRecord as UpdateAnnouncement; - const updateResponse: AnnouncementResponse = { - schemaId: jobData.schemaId, - blockNumber: blockNumer, - announcement: { - fromId: updateRecord.fromId, - announcementType: updateRecord.announcementType, - url: updateRecord.url, - contentHash: bases.base58btc.encode(updateRecord.contentHash as any), - targetAnnouncementType: updateRecord.targetAnnouncementType, - targetContentHash: bases.base58btc.encode(updateRecord.targetContentHash as any), - }, - requestId: jobRequestId, - }; - if (!(await this.isQueueFull(this.profileQueue))) { - const jobId = calculateJobId(updateResponse); - this.updateQueue.add('Update', updateResponse, { jobId }); - } - break; - } - default: - throw new Error(`Unknown announcement type ${JSON.stringify(mapRecord)}`); + let queue: Queue; + let typeName: string; + const announcementResponse = { + blockNumber, + requestId, + schemaId, + webhookUrl, + } as AnnouncementResponse; + + if (isBroadcast(mapRecord)) { + announcementResponse.announcement = { + fromId: mapRecord.fromId, + contentHash: bases.base16.encode(mapRecord.contentHash as never), + url: mapRecord.url, + announcementType: mapRecord.announcementType, + }; + queue = this.broadcastQueue; + typeName = 'Broadcast'; + } else if (isTombstone(mapRecord)) { + announcementResponse.announcement = { + fromId: mapRecord.fromId, + targetAnnouncementType: mapRecord.targetAnnouncementType, + targetContentHash: bases.base58btc.encode(mapRecord.targetContentHash as any), + announcementType: mapRecord.announcementType, + }; + queue = this.tombstoneQueue; + typeName = 'Tombstone'; + } else if (isReaction(mapRecord)) { + announcementResponse.announcement = { + fromId: mapRecord.fromId, + announcementType: mapRecord.announcementType, + inReplyTo: mapRecord.inReplyTo, + emoji: mapRecord.emoji, + apply: mapRecord.apply, + }; + queue = this.reactionQueue; + typeName = 'Reaction'; + } else if (isReply(mapRecord)) { + announcementResponse.announcement = { + fromId: mapRecord.fromId, + announcementType: mapRecord.announcementType, + url: mapRecord.url, + inReplyTo: mapRecord.inReplyTo, + contentHash: bases.base58btc.encode(mapRecord.contentHash as any), + }; + queue = this.replyQueue; + typeName = 'Reply'; + } else if (isProfile(mapRecord)) { + announcementResponse.announcement = { + fromId: mapRecord.fromId, + announcementType: mapRecord.announcementType, + url: mapRecord.url, + contentHash: bases.base58btc.encode(mapRecord.contentHash as any), + }; + queue = this.profileQueue; + typeName = 'Profile'; + } else if (isUpdate(mapRecord)) { + announcementResponse.announcement = { + fromId: mapRecord.fromId, + announcementType: mapRecord.announcementType, + url: mapRecord.url, + contentHash: bases.base58btc.encode(mapRecord.contentHash as any), + targetAnnouncementType: mapRecord.targetAnnouncementType, + targetContentHash: bases.base58btc.encode(mapRecord.targetContentHash as any), + }; + queue = this.updateQueue; + typeName = 'Update'; + } else { + throw new Error(`Unknown announcement type ${JSON.stringify(mapRecord)}`); } + await this.enqueueAnnouncementResponse(announcementResponse, typeName, queue); }); } @@ -210,6 +166,7 @@ export class IPFSContentProcessor extends BaseConsumer { const queueIsFull = queueStats.waiting + queueStats.active >= highWater; if (queueIsFull) { this.logger.log(`Queue ${queue.name} is full`); + // TODO: If queue is full, maybe throw a Delayed error? throw new Error(`Queue ${queue.name} is full`); } return queueIsFull; diff --git a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts index 72c2e62d..fabd469c 100644 --- a/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts +++ b/services/content-watcher/libs/common/src/ipfs/ipfs.module.ts @@ -1,138 +1,12 @@ -/* -https://docs.nestjs.com/modules -*/ - -import { BullModule } from '@nestjs/bullmq'; import { Module } from '@nestjs/common'; -import { EventEmitterModule } from '@nestjs/event-emitter'; -import { RedisModule } from '@songkeys/nestjs-redis'; -import { ConfigModule } from '../config/config.module'; -import { ConfigService } from '../config/config.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; -import * as QueueConstants from '../utils/queues'; import { IPFSContentProcessor } from './ipfs.dsnp'; import { IpfsService } from '../utils/ipfs.client'; @Module({ - imports: [ - BlockchainModule, - ConfigModule, - EventEmitterModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue( - { - name: QueueConstants.IPFS_QUEUE, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.BROADCAST_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.REACTION_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.REPLY_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.PROFILE_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.TOMBSTONE_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.UPDATE_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - ), - ], + imports: [BlockchainModule], controllers: [], providers: [IPFSContentProcessor, IpfsService], - exports: [BullModule, IPFSContentProcessor, IpfsService], + exports: [IPFSContentProcessor, IpfsService], }) export class IPFSProcessorModule {} diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts b/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts index 7b3adf8a..2f102f55 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/broadcast.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import * as QueueConstants from '../../utils/queues'; +import * as QueueConstants from '../../queues/queue-constants'; import { BaseConsumer } from '../../utils/base-consumer'; import { PubSubService } from '../pubsub.service'; import { AnnouncementResponse } from '../../types/content-announcement'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts b/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts index a13fcd9e..a79cff51 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/profile.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import * as QueueConstants from '../../utils/queues'; +import * as QueueConstants from '../../queues/queue-constants'; import { BaseConsumer } from '../../utils/base-consumer'; import { PubSubService } from '../pubsub.service'; import { AnnouncementResponse } from '../../types/content-announcement'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts b/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts index 121935a9..08ca8e0e 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/reaction.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import * as QueueConstants from '../../utils/queues'; +import * as QueueConstants from '../../queues/queue-constants'; import { BaseConsumer } from '../../utils/base-consumer'; import { PubSubService } from '../pubsub.service'; import { AnnouncementResponse } from '../../types/content-announcement'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts b/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts index e95333f1..ae03a568 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/reply.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import * as QueueConstants from '../../utils/queues'; +import * as QueueConstants from '../../queues/queue-constants'; import { BaseConsumer } from '../../utils/base-consumer'; import { PubSubService } from '../pubsub.service'; import { AnnouncementResponse } from '../../types/content-announcement'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts b/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts index 7d5cba51..a031369a 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/tombstone.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import * as QueueConstants from '../../utils/queues'; +import * as QueueConstants from '../../queues/queue-constants'; import { BaseConsumer } from '../../utils/base-consumer'; import { PubSubService } from '../pubsub.service'; import { AnnouncementResponse } from '../../types/content-announcement'; diff --git a/services/content-watcher/libs/common/src/pubsub/announcers/update.ts b/services/content-watcher/libs/common/src/pubsub/announcers/update.ts index 06cdace2..7aebd98c 100644 --- a/services/content-watcher/libs/common/src/pubsub/announcers/update.ts +++ b/services/content-watcher/libs/common/src/pubsub/announcers/update.ts @@ -1,7 +1,7 @@ import { Processor } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { Job } from 'bullmq'; -import * as QueueConstants from '../../utils/queues'; +import * as QueueConstants from '../../queues/queue-constants'; import { BaseConsumer } from '../../utils/base-consumer'; import { PubSubService } from '../pubsub.service'; import { AnnouncementResponse } from '../../types/content-announcement'; diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts index 963141e7..8f3d7f7b 100644 --- a/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.module.ts @@ -1,11 +1,5 @@ import { Module } from '@nestjs/common'; -import { EventEmitterModule } from '@nestjs/event-emitter'; -import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; -import { RedisModule } from '@songkeys/nestjs-redis'; -import { ConfigModule } from '../config/config.module'; -import { ConfigService } from '../config/config.service'; -import * as QueueConstants from '../utils/queues'; import { PubSubService } from './pubsub.service'; import { BroadcastSubscriber } from './announcers/broadcast'; import { ProfileSubscriber } from './announcers/profile'; @@ -15,129 +9,7 @@ import { TomstoneSubscriber } from './announcers/tombstone'; import { UpdateSubscriber } from './announcers/update'; @Module({ - imports: [ - ConfigModule, - RedisModule.forRootAsync( - { - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - config: [{ url: configService.redisUrl.toString() }], - }), - inject: [ConfigService], - }, - true, // isGlobal - ), - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue( - { - name: QueueConstants.BROADCAST_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.REPLY_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.REACTION_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.TOMBSTONE_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.PROFILE_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - { - name: QueueConstants.UPDATE_QUEUE_NAME, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }, - ), - EventEmitterModule.forRoot({ - // Use this instance throughout the application - global: true, - // set this to `true` to use wildcards - wildcard: false, - // the delimiter used to segment namespaces - delimiter: '.', - // set this to `true` if you want to emit the newListener event - newListener: false, - // set this to `true` if you want to emit the removeListener event - removeListener: false, - // the maximum amount of listeners that can be assigned to an event - maxListeners: 10, - // show event name in memory leak message when more than maximum amount of listeners is assigned - verboseMemoryLeak: false, - // disable throwing uncaughtException if an error event is emitted and it has no listeners - ignoreErrors: false, - }), - ScheduleModule.forRoot(), - ], + imports: [ScheduleModule], providers: [PubSubService, BroadcastSubscriber, ProfileSubscriber, ReactionSubscriber, ReplySubscriber, TomstoneSubscriber, UpdateSubscriber], controllers: [], exports: [PubSubService], diff --git a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts index fc5f8949..a5ba8606 100644 --- a/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts +++ b/services/content-watcher/libs/common/src/pubsub/pubsub.service.ts @@ -4,9 +4,10 @@ import Redis from 'ioredis'; import axios from 'axios'; import { MILLISECONDS_PER_SECOND } from 'time-constants'; import { EVENTS_TO_WATCH_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; -import { ConfigService } from '../config/config.service'; +import { AppConfigService } from '../config/config.service'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import { AnnouncementResponse } from '../types/content-announcement'; +import { IAnnouncementSubscription } from '../interfaces/announcement-subscription.interface'; @Injectable() export class PubSubService { @@ -14,7 +15,7 @@ export class PubSubService { constructor( @InjectRedis() private redis: Redis, - private readonly configService: ConfigService, + private readonly configService: AppConfigService, ) { this.logger = new Logger(this.constructor.name); } @@ -29,15 +30,20 @@ export class PubSubService { return; } // Get the registered webhooks for the specific messageType - const registeredWebhook = await this.redis.get(REGISTERED_WEBHOOK_KEY); - let currentWebhookRegistrationDtos: { announcementType: string; urls: string[] }[] = []; + let currentWebhookRegistrationDtos: IAnnouncementSubscription[] = []; + // A webhookUrl on a message means it was scanned by a targeted search and should not be broadcast to all registered webhooks + if (message?.webhookUrl) { + currentWebhookRegistrationDtos = [{ announcementType: messageType.toLowerCase(), urls: [message.webhookUrl] }]; + } else { + const registeredWebhook = message.webhookUrl ?? (await this.redis.get(REGISTERED_WEBHOOK_KEY)); - // Pause the queues since nobody is listening - if (!registeredWebhook) { - return; - } + // Pause the queues since nobody is listening + if (!registeredWebhook) { + return; + } - currentWebhookRegistrationDtos = JSON.parse(registeredWebhook); + currentWebhookRegistrationDtos = JSON.parse(registeredWebhook) as IAnnouncementSubscription[]; + } // Find the registrations for the specified messageType const registrationsForMessageType = currentWebhookRegistrationDtos.find((registration) => registration.announcementType === messageType.toLowerCase()); diff --git a/services/content-watcher/libs/common/src/utils/queues.ts b/services/content-watcher/libs/common/src/queues/queue-constants.ts similarity index 100% rename from services/content-watcher/libs/common/src/utils/queues.ts rename to services/content-watcher/libs/common/src/queues/queue-constants.ts diff --git a/services/content-watcher/libs/common/src/queues/queue.module.ts b/services/content-watcher/libs/common/src/queues/queue.module.ts new file mode 100644 index 00000000..ac00e92e --- /dev/null +++ b/services/content-watcher/libs/common/src/queues/queue.module.ts @@ -0,0 +1,125 @@ +import { BullModule } from '@nestjs/bullmq'; +import { Global, Module } from '@nestjs/common'; +import { AppConfigService } from '../config/config.service'; +import * as QueueConstants from './queue-constants'; + +@Global() +@Module({ + imports: [ + BullModule.forRootAsync({ + useFactory: (configService: AppConfigService) => { + // Note: BullMQ doesn't honor a URL for the Redis connection, and + // JS URL doesn't parse 'redis://' as a valid protocol, so we fool + // it by changing the URL to use 'http://' in order to parse out + // the host, port, username, password, etc. + // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but + // trying to keep the # of environment variables from proliferating + const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); + const { hostname, port, username, password, pathname } = url; + return { + connection: { + host: hostname || undefined, + port: port ? Number(port) : undefined, + username: username || undefined, + password: password || undefined, + db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, + }, + prefix: `${configService.cacheKeyPrefix}:bull`, + }; + }, + inject: [AppConfigService], + }), + BullModule.registerQueue( + { + name: QueueConstants.REQUEST_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.IPFS_QUEUE, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.BROADCAST_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.REPLY_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.REACTION_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.TOMBSTONE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.PROFILE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + { + name: QueueConstants.UPDATE_QUEUE_NAME, + defaultJobOptions: { + attempts: 3, + backoff: { + type: 'exponential', + }, + removeOnComplete: true, + removeOnFail: false, + }, + }, + ), + ], + exports: [BullModule], +}) +export class QueueModule {} diff --git a/services/content-watcher/libs/common/src/scanner/scanner.module.ts b/services/content-watcher/libs/common/src/scanner/scanner.module.ts index 4f59c877..990506c7 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.module.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.module.ts @@ -1,57 +1,11 @@ -/* -https://docs.nestjs.com/modules -*/ - import '@frequency-chain/api-augment'; import { Module } from '@nestjs/common'; -import { BullModule } from '@nestjs/bullmq'; import { ScheduleModule } from '@nestjs/schedule'; -import { ConfigModule } from '../config/config.module'; -import { ScannerService } from './scanner'; +import { ScannerService } from './scanner.service'; import { BlockchainModule } from '../blockchain/blockchain.module'; -import { ConfigService } from '../config/config.service'; -import * as QueueConstants from '../utils/queues'; @Module({ - imports: [ - ConfigModule, - BlockchainModule, - BullModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => { - // Note: BullMQ doesn't honor a URL for the Redis connection, and - // JS URL doesn't parse 'redis://' as a valid protocol, so we fool - // it by changing the URL to use 'http://' in order to parse out - // the host, port, username, password, etc. - // We could pass REDIS_HOST, REDIS_PORT, etc, in the environment, but - // trying to keep the # of environment variables from proliferating - const url = new URL(configService.redisUrl.toString().replace(/^redis[s]*/, 'http')); - const { hostname, port, username, password, pathname } = url; - return { - connection: { - host: hostname || undefined, - port: port ? Number(port) : undefined, - username: username || undefined, - password: password || undefined, - db: pathname?.length > 1 ? Number(pathname.slice(1)) : undefined, - }, - }; - }, - inject: [ConfigService], - }), - BullModule.registerQueue({ - name: QueueConstants.IPFS_QUEUE, - defaultJobOptions: { - attempts: 3, - backoff: { - type: 'exponential', - }, - removeOnComplete: true, - removeOnFail: false, - }, - }), - ScheduleModule.forRoot(), - ], + imports: [BlockchainModule, ScheduleModule], controllers: [], providers: [ScannerService], exports: [ScannerService], diff --git a/services/content-watcher/libs/common/src/scanner/scanner.ts b/services/content-watcher/libs/common/src/scanner/scanner.service.ts similarity index 86% rename from services/content-watcher/libs/common/src/scanner/scanner.ts rename to services/content-watcher/libs/common/src/scanner/scanner.service.ts index 341fbeed..28565da2 100644 --- a/services/content-watcher/libs/common/src/scanner/scanner.ts +++ b/services/content-watcher/libs/common/src/scanner/scanner.service.ts @@ -6,11 +6,11 @@ import { InjectQueue } from '@nestjs/bullmq'; import Redis from 'ioredis'; import { InjectRedis } from '@songkeys/nestjs-redis'; import { SchedulerRegistry } from '@nestjs/schedule'; -import { MILLISECONDS_PER_SECOND, SECONDS_PER_MINUTE } from 'time-constants'; +import { MILLISECONDS_PER_SECOND } from 'time-constants'; import { Queue } from 'bullmq'; -import { ConfigService } from '../config/config.service'; +import { AppConfigService } from '../config/config.service'; import { BlockchainService } from '../blockchain/blockchain.service'; -import * as QueueConstants from '../utils/queues'; +import * as QueueConstants from '../queues/queue-constants'; import { EVENTS_TO_WATCH_KEY, LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY, REGISTERED_WEBHOOK_KEY } from '../constants'; import { ChainWatchOptionsDto } from '../dtos/chain.watch.dto'; import * as RedisUtils from '../utils/redis'; @@ -28,7 +28,7 @@ export class ScannerService implements OnApplicationBootstrap, OnApplicationShut private scanResetBlockNumber: number | undefined; constructor( - private readonly configService: ConfigService, + private readonly configService: AppConfigService, private readonly blockchainService: BlockchainService, @InjectRedis() private readonly cache: Redis, @InjectQueue(QueueConstants.IPFS_QUEUE) private readonly ipfsQueue: Queue, @@ -39,11 +39,6 @@ export class ScannerService implements OnApplicationBootstrap, OnApplicationShut } async onApplicationBootstrap() { - const startingBlock = this.configService.startingBlock; - if (startingBlock) { - this.logger.log(`Setting initial scan block to ${startingBlock}`); - this.setLastSeenBlockNumber(startingBlock - 1); - } setImmediate(() => this.scan()); const scanInterval = this.configService.blockchainScanIntervalSeconds * MILLISECONDS_PER_SECOND; @@ -54,8 +49,9 @@ export class ScannerService implements OnApplicationBootstrap, OnApplicationShut } onApplicationShutdown(_signal?: string | undefined) { - const interval = this.schedulerRegistry.getInterval(INTERVAL_SCAN_NAME); - clearInterval(interval); + if (this.schedulerRegistry.doesExist('interval', INTERVAL_SCAN_NAME)) { + clearInterval(this.schedulerRegistry.getInterval(INTERVAL_SCAN_NAME)); + } } public pauseScanner() { @@ -83,24 +79,22 @@ export class ScannerService implements OnApplicationBootstrap, OnApplicationShut async scan() { try { - this.logger.debug('Starting scanner'); - if (this.scanInProgress) { - this.logger.debug('Scan already in progress'); return; } const registeredWebhook = await this.cache.get(REGISTERED_WEBHOOK_KEY); if (!registeredWebhook) { - this.logger.log('No registered webhooks; no scan performed.'); return; } + this.logger.debug('Starting scanner'); const chainWatchFilters = await this.cache.get(EVENTS_TO_WATCH_KEY); const eventsToWatch: ChainWatchOptionsDto = chainWatchFilters ? JSON.parse(chainWatchFilters) : { msa_ids: [], schemaIds: [] }; this.scanInProgress = true; let first = true; + // eslint-disable-next-line no-constant-condition while (true) { if (this.paused) { this.logger.log('Scan paused'); @@ -146,7 +140,12 @@ export class ScannerService implements OnApplicationBootstrap, OnApplicationShut nextBlock = this.scanResetBlockNumber; this.scanResetBlockNumber = undefined; } else { - nextBlock = (Number(await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? 0) + 1; + nextBlock = Number((await this.cache.get(LAST_SEEN_BLOCK_NUMBER_SCANNER_KEY)) ?? '0'); + if (!nextBlock) { + nextBlock = await this.blockchainService.getLatestFinalizedBlockNumber(); + } + + nextBlock += 1; } return nextBlock; diff --git a/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts b/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts index 9a05a885..99a0516e 100644 --- a/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts +++ b/services/content-watcher/libs/common/src/types/content-announcement/types.gen.ts @@ -14,7 +14,11 @@ export type AnnouncementResponse = { /** * An optional identifier for the request, may be used for tracking or correlation */ - requestId?: string | null; + requestId?: string; + /** + * An optional webhook URL registered as part of a specific search request + */ + webhookUrl?: string; /** * Identifier for the schema being used or referenced */ diff --git a/services/content-watcher/libs/common/src/utils/ipfs.client.ts b/services/content-watcher/libs/common/src/utils/ipfs.client.ts index c7547990..76bba229 100644 --- a/services/content-watcher/libs/common/src/utils/ipfs.client.ts +++ b/services/content-watcher/libs/common/src/utils/ipfs.client.ts @@ -9,7 +9,7 @@ import { blake2b256 as hasher } from '@multiformats/blake2/blake2b'; import { create } from 'multiformats/hashes/digest'; import { randomUUID } from 'crypto'; import { base58btc } from 'multiformats/bases/base58'; -import { ConfigService } from '../config/config.service'; +import { AppConfigService } from '../config/config.service'; export interface FilePin { cid: string; @@ -23,7 +23,7 @@ export interface FilePin { export class IpfsService { logger: Logger; - constructor(private readonly configService: ConfigService) { + constructor(private readonly configService: AppConfigService) { this.logger = new Logger(IpfsService.name); } diff --git a/services/content-watcher/libs/common/src/utils/type-guards.ts b/services/content-watcher/libs/common/src/utils/type-guards.ts new file mode 100644 index 00000000..9827402f --- /dev/null +++ b/services/content-watcher/libs/common/src/utils/type-guards.ts @@ -0,0 +1,41 @@ +import { + AnnouncementType, + BroadcastAnnouncement, + ProfileAnnouncement, + ReactionAnnouncement, + ReplyAnnouncement, + TombstoneAnnouncement, + TypedAnnouncement, + UpdateAnnouncement, +} from '../types/content-announcement'; + +export function isTypedAnnouncement(obj: object): obj is TypedAnnouncement { + if ('announcementType' in obj) { + return true; + } + + return false; +} +export function isTombstone(obj: object): obj is TombstoneAnnouncement { + return isTypedAnnouncement(obj) && obj.announcementType === AnnouncementType.Tombstone; +} + +export function isBroadcast(obj: object): obj is BroadcastAnnouncement { + return isTypedAnnouncement(obj) && obj.announcementType === AnnouncementType.Broadcast; +} + +export function isReply(obj: object): obj is ReplyAnnouncement { + return isTypedAnnouncement(obj) && obj.announcementType === AnnouncementType.Reply; +} + +export function isReaction(obj: object): obj is ReactionAnnouncement { + return isTypedAnnouncement(obj) && obj.announcementType === AnnouncementType.Reaction; +} + +export function isUpdate(obj: object): obj is UpdateAnnouncement { + return isTypedAnnouncement(obj) && obj.announcementType === AnnouncementType.Update; +} + +export function isProfile(obj: object): obj is ProfileAnnouncement { + return isTypedAnnouncement(obj) && obj.announcementType === AnnouncementType.Profile; +} diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index 6946fb0f..b2e216ff 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -4,11 +4,11 @@ "description": "Services to publish content on DSNP/Frequency", "main": "dist/apps/api/main.js", "scripts": { - "start:api": "nest start api", - "start:api:watch": "nest start api --watch", - "start:api:prod": "node dist/apps/api/main.js", - "start:api:dev": "set -a ; . .env ; nest start api --watch", - "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", + "start": "nest start api", + "start:watch": "nest start api --watch", + "start:prod": "node dist/apps/api/main.js", + "start:dev": "set -a ; . .env ; nest start api --watch", + "start:debug": "set -a ; . .env ; nest start api --debug --watch", "build": "nest build", "build:swagger": "npx ts-node -r tsconfig-paths/register apps/api/src/build-openapi.ts", "build:metadata": "npx ts-node apps/api/src/generate-metadata.ts", diff --git a/services/content-watcher/swagger.json b/services/content-watcher/swagger.json index f4d078f2..f819d06b 100644 --- a/services/content-watcher/swagger.json +++ b/services/content-watcher/swagger.json @@ -323,25 +323,25 @@ "type": "string" } } - }, - "required": [ - "schemaIds", - "dsnpIds" - ] + } }, "ContentSearchRequestDto": { "type": "object", "properties": { + "clientReferenceId": { + "type": "string", + "description": "An optional client-supplied reference ID by which it can identify the result of this search" + }, "startBlock": { "type": "number", "minimum": 1, - "description": "The starting block number to search from", + "description": "The block number to search (backward) from", "example": 100 }, - "endBlock": { + "blockCount": { "type": "number", "minimum": 1, - "description": "The ending block number to search to", + "description": "The number of blocks to scan (backwards)", "example": 101 }, "filters": { @@ -352,15 +352,14 @@ } ] }, - "id": { - "type": "string" + "webhookUrl": { + "type": "string", + "description": "A webhook URL to be notified of the results of this search" } }, "required": [ - "startBlock", - "endBlock", - "filters", - "id" + "blockCount", + "webhookUrl" ] }, "WebhookRegistrationDto": { From 7167f5f68fa646733799184aeeb571ab8604498e Mon Sep 17 00:00:00 2001 From: Wil Wade Date: Fri, 19 Jul 2024 20:54:03 +0000 Subject: [PATCH 137/137] Move github workflows --- .../workflows/content-watcher-build.yml | 32 +++++++----- .../workflows/content-watcher-release.yml | 24 ++++----- .../content-watcher/.github/dependabot.yml | 11 ---- .../common/is-full-release/action.yml | 22 -------- .../.github/workflows/deploy-gh-pages.yaml | 51 ------------------- 5 files changed, 31 insertions(+), 109 deletions(-) rename services/content-watcher/.github/workflows/build.yml => .github/workflows/content-watcher-build.yml (61%) rename services/content-watcher/.github/workflows/release.yml => .github/workflows/content-watcher-release.yml (72%) delete mode 100644 services/content-watcher/.github/dependabot.yml delete mode 100644 services/content-watcher/.github/workflows/common/is-full-release/action.yml delete mode 100644 services/content-watcher/.github/workflows/deploy-gh-pages.yaml diff --git a/services/content-watcher/.github/workflows/build.yml b/.github/workflows/content-watcher-build.yml similarity index 61% rename from services/content-watcher/.github/workflows/build.yml rename to .github/workflows/content-watcher-build.yml index b69ad702..4746b25f 100644 --- a/services/content-watcher/.github/workflows/build.yml +++ b/.github/workflows/content-watcher-build.yml @@ -1,4 +1,4 @@ -name: Build And Test Content Watcher Service +name: "[Content Watcher] Build And Test" concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: true @@ -12,7 +12,7 @@ on: jobs: build: - name: 'Build' + name: "Build" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -20,15 +20,17 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - cache: 'npm' - registry-url: 'https://registry.npmjs.org' - cache-dependency-path: package-lock.json + cache: "npm" + registry-url: "https://registry.npmjs.org" + cache-dependency-path: services/content-watcher/package-lock.json - name: Install dependencies + working-directory: services/content-watcher run: npm ci - name: Build NestJS + working-directory: services/content-watcher run: npm run build test_jest: - name: 'Test' + name: "Test" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -36,15 +38,17 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - cache: 'npm' - registry-url: 'https://registry.npmjs.org' - cache-dependency-path: package-lock.json + cache: "npm" + registry-url: "https://registry.npmjs.org" + cache-dependency-path: services/content-watcher/package-lock.json - name: Install dependencies + working-directory: services/content-watcher run: npm ci - name: Run Jest + working-directory: services/content-watcher run: npm run test check_licenses: - name: 'Dependency License Check' + name: "Dependency License Check" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -52,11 +56,13 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 - cache: 'npm' - registry-url: 'https://registry.npmjs.org' - cache-dependency-path: package-lock.json + cache: "npm" + registry-url: "https://registry.npmjs.org" + cache-dependency-path: services/content-watcher/package-lock.json - name: Install dependencies + working-directory: services/content-watcher run: npm ci - name: License Check + working-directory: services/content-watcher # List all the licenses and error out if it is not one of the supported licenses run: npx license-report --fields=name --fields=licenseType | jq 'map(select(.licenseType | IN("MIT", "Apache-2.0", "ISC", "BSD-3-Clause", "BSD-2-Clause", "(Apache-2.0 AND MIT)") | not)) | if length == 0 then halt else halt_error(1) end' diff --git a/services/content-watcher/.github/workflows/release.yml b/.github/workflows/content-watcher-release.yml similarity index 72% rename from services/content-watcher/.github/workflows/release.yml rename to .github/workflows/content-watcher-release.yml index 9e3c13f8..b65343f6 100644 --- a/services/content-watcher/.github/workflows/release.yml +++ b/.github/workflows/content-watcher-release.yml @@ -1,24 +1,24 @@ -name: Release -run-name: Cut Release ${{github.event.inputs.release-version || github.ref_name}} +name: "[Content Watcher] Release" +run-name: "[Content Watcher] Cut Release ${{github.event.inputs.release-version || github.ref_name}}" concurrency: group: ${{github.workflow}}-${{github.ref}} cancel-in-progress: true on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+' # ex. v1.0.0 - - 'v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+' # ex. v1.1.0-rc1 - - 'v0.0.1' # used for testing only - - 'v0.0.1-rc[0-9]+' # used for testing only + - "content-watcher-v[0-9]+.[0-9]+.[0-9]+" # ex. v1.0.0 + - "content-watcher-v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+" # ex. v1.1.0-rc1 + - "content-watcher-v0.0.1" # used for testing only + - "content-watcher-v0.0.1-rc[0-9]+" # used for testing only workflow_dispatch: inputs: release-version: - description: 'Release version (v#.#.#[-rc#])' + description: "Release version (content-watcher-v#.#.#[-rc#])" required: true env: NEW_RELEASE_TAG_FROM_UI: ${{github.event.inputs.release-version}} - TEST_RUN: ${{startsWith(github.event.inputs.release-version || github.ref_name, 'v0.0.1')}} + TEST_RUN: ${{startsWith(github.event.inputs.release-version || github.ref_name, 'content-watcher-v0.0.1')}} DOCKER_HUB_PROFILE: amplicalabs IMAGE_NAME: content-watcher-service @@ -33,10 +33,10 @@ jobs: run: | version=${{env.NEW_RELEASE_TAG_FROM_UI}} echo "Release version entered in UI: $version" - regex='^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-rc[1-9]\d*)?$' + regex='^content-watcher-v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-rc[1-9]\d*)?$' if [[ ! $version =~ $regex ]]; then echo "ERROR: Entered version $version is not valid." - echo "Please use v#.#.#[-rc#] format." + echo "Please use content-watcher-v#.#.#[-rc#] format." exit 1 fi echo "valid-version=true" >> $GITHUB_OUTPUT @@ -69,8 +69,8 @@ jobs: - name: Build and Push content-watcher-service Image uses: docker/build-push-action@v5 with: - context: . + context: services/content-watcher platforms: linux/amd64 push: ${{env.TEST_RUN != 'true'}} - file: ./Dockerfile + file: services/content-watcher/Dockerfile tags: ${{ steps.cp-tags.outputs.tags }} diff --git a/services/content-watcher/.github/dependabot.yml b/services/content-watcher/.github/dependabot.yml deleted file mode 100644 index d91a92ad..00000000 --- a/services/content-watcher/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - -version: 2 -updates: - - package-ecosystem: 'npm' # See documentation for possible values - directory: '/' # Location of package manifests - schedule: - interval: 'weekly' diff --git a/services/content-watcher/.github/workflows/common/is-full-release/action.yml b/services/content-watcher/.github/workflows/common/is-full-release/action.yml deleted file mode 100644 index 9e17770c..00000000 --- a/services/content-watcher/.github/workflows/common/is-full-release/action.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Is Full Release? -description: Determines whether the version tag represents a full release -inputs: - version-tag: - description: "Version tag in v#.#.#[-*] format" - required: true -outputs: - is-full-release: - description: "'true' if full release, 'false' otherwise" - value: ${{steps.is-full-release.outputs.is_full_release}} -runs: - using: "composite" - steps: - - name: Full Release? - id: is-full-release - shell: bash - run: | - version_tag=${{inputs.version-tag}} - is_full_release=$([[ "$version_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] && \ - echo 'true' || echo 'false') - echo "is_full_release: $is_full_release" - echo "is_full_release=$is_full_release" >> $GITHUB_OUTPUT diff --git a/services/content-watcher/.github/workflows/deploy-gh-pages.yaml b/services/content-watcher/.github/workflows/deploy-gh-pages.yaml deleted file mode 100644 index 660b20dd..00000000 --- a/services/content-watcher/.github/workflows/deploy-gh-pages.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: Build and Publish OpenAPI UI to Pages -concurrency: - group: ${{github.workflow}}-${{github.ref}} - cancel-in-progress: true -on: - workflow_dispatch: - push: - branches: - - main - -permissions: - contents: read - pages: write - id-token: write - -jobs: - build: - name: 'Build Pages Artifact' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Generate Swagger UI - run: npm run generate-swagger-ui - - - name: Setup Pages - id: pages - uses: actions/configure-pages@v4 - - - name: Publish generated swagger.html to GitHub Pages - uses: actions/upload-pages-artifact@v3 - with: - path: ./docs - - # Deployment job - deploy: - name: 'Deploy to Pages' - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4
Release notes

Sourced from @​nestjs/config's releases.

Release 3.2.3

  • Merge branch 'Motii1-remove-unnecessary-uuid' (faa8f59)
  • chore: resolve conflicts (0045924)
  • Merge pull request #1752 from nestjs/renovate/nest-monorepo (f039417)
  • chore(deps): update nest monorepo to v10.3.10 (00febb5)
  • Merge pull request #1698 from nestjs/renovate/cimg-node-22.x (b2ef5a8)
  • Merge pull request #1751 from nestjs/renovate/release-it-17.x (81534ba)
  • Merge pull request #1746 from micalevisk/patch-1 (ea180e4)
  • chore(deps): update dependency release-it to v17.4.1 (3b03bd9)
  • chore(deps): update dependency @​types/lodash to v4.17.6 (112b556)
  • chore(deps): update dependency @​types/node to v20.14.9 (295709b)
  • chore(deps): update typescript-eslint monorepo to v7.14.1 (17058a4)
  • chore(deps): update dependency @​types/node to v20.14.8 (cf1fbc6)
  • feat: less verbose module referencing on errors/debug messages (0f0176a)
  • chore(deps): update dependency release-it to v17.4.0 (56497be)
  • chore(deps): update dependency @​types/node to v20.14.7 (e31959c)
  • chore(deps): update dependency typescript to v5.5.2 (ec9b67d)
  • chore(deps): update node.js to v22 (2b7858c)
  • chore(deps): update dependency joi to v17.13.3 (2bffa1a)
  • chore(deps): update dependency @​types/node to v20.14.6 (628dd4a)
  • chore(deps): update dependency joi to v17.13.2 (38c5f04)
  • chore(deps): update dependency @​types/node to v20.14.5 (3f1efcf)
  • chore(deps): update dependency @​types/node to v20.14.4 (ac86947)
  • chore(deps): update typescript-eslint monorepo to v7.13.1 (41c68f5)
  • chore(deps): update dependency ts-jest to v29.1.5 (8948ce8)
  • chore(deps): update dependency lint-staged to v15.2.7 (bb338b8)
  • chore(deps): update dependency lint-staged to v15.2.6 (bfa8fc4)
  • chore(deps): update dependency prettier to v3.3.2 (84814cd)
  • chore(deps): update typescript-eslint monorepo to v7.13.0 (20dce4e)
  • chore(deps): update dependency @​types/lodash to v4.17.5 (e0aa801)
  • chore(deps): update dependency prettier to v3.3.1 (e400d73)
  • chore(deps): update dependency @​types/node to v20.14.2 (b53e8e6)
  • chore(deps): update dependency @​types/node to v20.14.1 (7542203)
  • chore(deps): update typescript-eslint monorepo to v7.12.0 (971bb0d)
  • chore(deps): update nest monorepo to v10.3.9 (a5be017)
  • chore(deps): update dependency @​types/node to v20.14.0 (63c18ce)
  • chore(deps): update dependency prettier to v3.3.0 (bfb3346)
  • chore(deps): update dependency @​types/node to v20.13.0 (91fa417)
  • chore(deps): update dependency @​types/node to v20.12.13 (fab616f)
  • chore(deps): update dependency ts-jest to v29.1.4 (0d1c2d0)
  • chore(deps): update typescript-eslint monorepo to v7.11.0 (e054a60)
  • chore(deps): update dependency lint-staged to v15.2.5 (f08464a)
  • chore(deps): update dependency lint-staged to v15.2.4 (bee9517)
  • chore(deps): update dependency ts-jest to v29.1.3 (eeb5c99)
  • chore(deps): update dependency release-it to v17.3.0 (18777c2)
  • chore(deps): update typescript-eslint monorepo to v7.10.0 (56e9c84)
  • chore(deps): update dependency @​types/lodash to v4.17.4 (6720586)
  • chore(deps): update dependency @​types/node to v20.12.12 (be876a5)
  • chore(deps): update typescript-eslint monorepo to v7.9.0 (6e765c2)
  • chore(deps): update dependency rimraf to v5.0.7 (7cb161d)

... (truncated)

c>7cM(4(e@?_R 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 literal 0 HcmV?d00001 diff --git a/services/content-watcher/env.template b/services/content-watcher/env.template index 0422107e..e0e17d08 100644 --- a/services/content-watcher/env.template +++ b/services/content-watcher/env.template @@ -1,14 +1,14 @@ -# Copy this file to ".env.dev" and ".env.docker.dev", and then tweak values for local development +# Copy this file to ".env" and ".env.docker.dev", and then tweak values for local development # URL to IPFS endpoint # IPFS_ENDPOINT="https://ipfs.infura.io:5001" IPFS_ENDPOINT="http://127.0.0.1:5001" -# If using Infura, put Project ID here, or leave blank for Kubo RPC +# If using Infura with auth required for read access, put Project ID here, or leave blank for Kubo RPC # IPFS_BASIC_AUTH_USER= -# If using Infura, put auth token here, or leave blank for Kubo RPC -IPFS_BASIC_AUTH_SECRET= +# If using Infura with auth required for read access, put auth token here, or leave blank for Kubo RPC +# IPFS_BASIC_AUTH_SECRET= # IPFS gateway URL. '[CID]' is a token that will be replaced with an actual content ID # IPFS_GATEWAY_URL="https://ipfs.io/ipfs/[CID]" diff --git a/services/content-watcher/jest-setup.ts b/services/content-watcher/jest-setup.ts new file mode 100644 index 00000000..1b5c96e5 --- /dev/null +++ b/services/content-watcher/jest-setup.ts @@ -0,0 +1,2 @@ +import dotenv from 'dotenv'; +dotenv.config({ path: 'env.template', override: true }); diff --git a/services/content-watcher/package-lock.json b/services/content-watcher/package-lock.json index e483f953..e3f2d05a 100644 --- a/services/content-watcher/package-lock.json +++ b/services/content-watcher/package-lock.json @@ -6035,11 +6035,11 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -7801,9 +7801,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -12884,9 +12884,9 @@ } }, "node_modules/thrift/node_modules/ws": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", - "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.4.tgz", + "integrity": "sha512-fFCejsuC8f9kOSu9FYaOw8CdO68O3h5v0lg4p74o8JqWpwTf9tniOD+nOB78aWoVSS6WptVUmDrp/KPsMVBWFQ==", "dependencies": { "async-limiter": "~1.0.0" } @@ -13724,9 +13724,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "engines": { "node": ">=10.0.0" }, diff --git a/services/content-watcher/package.json b/services/content-watcher/package.json index f64dab00..a5962a17 100644 --- a/services/content-watcher/package.json +++ b/services/content-watcher/package.json @@ -4,15 +4,15 @@ "description": "Services to publish content on DSNP/Frequency", "main": "dist/apps/api/main.js", "scripts": { + "start:api": "nest start api", + "start:api:watch": "nest start api --watch", + "start:api:prod": "node dist/apps/api/main.js", + "start:api:dev": "set -a ; . .env ; nest start api --watch", + "start:api:debug": "set -a ; . .env ; nest start api --debug --watch", "build": "nest build", "build:swagger": "npx ts-node apps/api/src/generate-metadata.ts", "generate-swagger-ui": "npx --yes @redocly/cli build-docs swagger.yaml --output=./docs/index.html", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", - "start": "nest start api", - "start:watch": "nest start api --watch", - "start:prod": "node dist/apps/api/main.js", - "start:dev": "set -a ; . .env ; nest start api", - "start:debug": "set -a ; . .env ; nest start api --debug --watch", "docker-build": "docker build -t content-watcher-service .", "docker-build:dev": "docker-compose build", "docker-run": " build -t content-watcher-service-deploy . ; docker run -p 6379:6379 --env-file .env content-watcher-service-deploy", @@ -20,11 +20,11 @@ "docker-stop:dev": "docker-compose stop", "clean": "rm -Rf dist", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", - "pretest": "cp env.template .env", "test": "jest --coverage --verbose", - "test:e2e": "set -a ; . ./.env.dev; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", + "test:e2e": "set -a ; . ./.env; jest --config ./apps/api/test/jest-e2e.json --detectOpenHandles", "local:init": "node scripts/chain-setup/local-chain-setup.cjs", "local:publish": "cd scripts/content-setup && npm i && npm run main", + "local:webhook": "node scripts/webhook-cat.cjs", "generate-content-announcement-types": "npx @hey-api/openapi-ts -i content-announcement.openapi.json -o libs/common/src/types/content-announcement" }, "repository": { @@ -102,31 +102,18 @@ "typescript-eslint": "^7.9.0" }, "jest": { - "moduleFileExtensions": [ - "js", - "json", - "ts" - ], + "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", - "setupFiles": [ - "dotenv/config" - ], + "setupFiles": ["./jest-setup.ts"], "testRegex": ".*\\.spec\\.ts$", - "testPathIgnorePatterns": [ - ".*\\.mock\\.spec\\.ts$" - ], + "testPathIgnorePatterns": [".*\\.mock\\.spec\\.ts$"], "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "collectCoverageFrom": [ - "**/*.(t|j)s" - ], + "collectCoverageFrom": ["**/*.(t|j)s"], "coverageDirectory": "./coverage", "testEnvironment": "node", - "roots": [ - "/apps/", - "/libs/" - ], + "roots": ["/apps/", "/libs/"], "moduleNameMapper": { "^@content-watcher-common(|/.*)$": "/libs/common/src/$1" } diff --git a/services/content-watcher/scripts/chain-setup/local-chain-setup.cjs b/services/content-watcher/scripts/chain-setup/local-chain-setup.cjs index 2d518eff..b43815c9 100644 --- a/services/content-watcher/scripts/chain-setup/local-chain-setup.cjs +++ b/services/content-watcher/scripts/chain-setup/local-chain-setup.cjs @@ -1,6 +1,6 @@ -const { options } = require("@frequency-chain/api-augment"); -const { WsProvider, ApiPromise, Keyring } = require("@polkadot/api"); -const { deploy } = require("@dsnp/frequency-schemas/cli/deploy"); +const { options } = require('@frequency-chain/api-augment'); +const { WsProvider, ApiPromise, Keyring } = require('@polkadot/api'); +const { deploy } = require('@dsnp/frequency-schemas/cli/deploy'); // Given a list of events, a section and a method, // returns the first event with matching section and method. @@ -10,82 +10,88 @@ const eventWithSectionAndMethod = (events, section, method) => { }; const main = async () => { - console.log("A quick script that will setup a clean localhost instance of Frequency for DSNP "); + console.log('A quick script that will setup a clean localhost instance of Frequency for DSNP '); - const providerUri = "ws://127.0.0.1:9944"; + const providerUri = 'ws://127.0.0.1:9944'; const provider = new WsProvider(providerUri); const api = await ApiPromise.create({ provider, throwOnConnect: true, ...options }); - const keys = new Keyring().addFromUri("//Alice", {}, "sr25519"); + const keys = new Keyring().addFromUri('//Alice', {}, 'sr25519'); // Create alice msa await new Promise((resolve, reject) => { - console.log("Creating an MSA..."); - api.tx.msa.create() - .signAndSend(keys, {}, ({ status, events, dispatchError }) => { - if (dispatchError) { - console.error("ERROR: ", dispatchError.toHuman()); + console.log('Creating an MSA...'); + api.tx.msa.create().signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error('ERROR: ', dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, 'msa', 'MsaCreated'); + if (evt) { + const id = evt?.data[0]; + console.log('SUCCESS: MSA Created:' + id); + resolve(); + } else { + console.error( + 'ERROR: Expected event not found', + events.map((x) => x.toHuman()), + ); reject(); - } else if (status.isInBlock || status.isFinalized) { - const evt = eventWithSectionAndMethod(events, "msa", "MsaCreated"); - if (evt) { - const id = evt?.data[0]; - console.log("SUCCESS: MSA Created:" + id); - resolve(); - } else { - console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); - reject(); - } } - }); + } + }); }); // Create alice provider await new Promise((resolve, reject) => { - console.log("Creating an Provider..."); - api.tx.msa.createProvider("alice") - .signAndSend(keys, {}, ({ status, events, dispatchError }) => { - if (dispatchError) { - console.error("ERROR: ", dispatchError.toHuman()); + console.log('Creating an Provider...'); + api.tx.msa.createProvider('alice').signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error('ERROR: ', dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, 'msa', 'ProviderCreated'); + if (evt) { + const id = evt?.data[0]; + console.log('SUCCESS: Provider Created:' + id); + resolve(); + } else { + console.error( + 'ERROR: Expected event not found', + events.map((x) => x.toHuman()), + ); reject(); - } else if (status.isInBlock || status.isFinalized) { - const evt = eventWithSectionAndMethod(events, "msa", "ProviderCreated"); - if (evt) { - const id = evt?.data[0]; - console.log("SUCCESS: Provider Created:" + id); - resolve(); - } else { - console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); - reject(); - } } - }); + } + }); }); // Alice provider get Capacity await new Promise((resolve, reject) => { - console.log("Staking for Capacity..."); - api.tx.capacity.stake("1", 500_000 * Math.pow(8, 10)) - .signAndSend(keys, {}, ({ status, events, dispatchError }) => { - if (dispatchError) { - console.error("ERROR: ", dispatchError.toHuman()); + console.log('Staking for Capacity...'); + api.tx.capacity.stake('1', 500_000 * Math.pow(8, 10)).signAndSend(keys, {}, ({ status, events, dispatchError }) => { + if (dispatchError) { + console.error('ERROR: ', dispatchError.toHuman()); + reject(); + } else if (status.isInBlock || status.isFinalized) { + const evt = eventWithSectionAndMethod(events, 'capacity', 'Staked'); + if (evt) { + console.log('SUCCESS: Provider Staked:', evt.data.toHuman()); + resolve(); + } else { + console.error( + 'ERROR: Expected event not found', + events.map((x) => x.toHuman()), + ); reject(); - } else if (status.isInBlock || status.isFinalized) { - const evt = eventWithSectionAndMethod(events, "capacity", "Staked"); - if (evt) { - console.log("SUCCESS: Provider Staked:", evt.data.toHuman()); - resolve(); - } else { - console.error("ERROR: Expected event not found", events.map(x => x.toHuman())); - reject(); - } } - }); + } + }); }); // Deploy Schemas await deploy(); - console.log("Setup Complete!"); -} + console.log('Setup Complete!'); +}; main().catch(console.error).finally(process.exit); diff --git a/services/content-watcher/scripts/content-setup/index.js b/services/content-watcher/scripts/content-setup/index.js index 54bf99e0..d46739af 100644 --- a/services/content-watcher/scripts/content-setup/index.js +++ b/services/content-watcher/scripts/content-setup/index.js @@ -1,50 +1,56 @@ - import axios from 'axios'; +// Use random strings and emoji each run so that the content is new each time +const randomString = Array(10) + .fill(null) + .map(() => Math.round(Math.random() * 16).toString(16)) + .join(''); +const randomEmoji = String.fromCodePoint(Math.floor(Math.random() * (0x1f57f - 0x1f519 + 1)) + 0x1f519); + const validLocation = { - name: 'name of location', - accuracy: 97, - altitude: 10, - latitude: 37.26, - longitude: -119.59, - radius: 10, - units: 'm', - }; - const validTags = [ - { - type: 'mention', - mentionedId: 'dsnp://78187493520', - }, + name: 'name of location', + accuracy: 97, + altitude: 10, + latitude: 37.26, + longitude: -119.59, + radius: 10, + units: 'm', +}; +const validTags = [ + { + type: 'mention', + mentionedId: 'dsnp://78187493520', + }, + { + type: 'hashtag', + name: '#taggedUser', + }, +]; +const validContentNoUploadedAssets = { + content: `test broadcast message with Random: ${randomString}`, + published: '1970-01-01T00:00:00+00:00', + name: 'name of note content', + assets: [ { - type: 'hashtag', - name: '#taggedUser', + type: 'link', + name: 'link asset', + href: 'http://example.com', }, - ]; - const validContentNoUploadedAssets = { - content: 'test broadcast message', - published: '1970-01-01T00:00:00+00:00', - name: 'name of note content', - assets: [ - { - type: 'link', - name: 'link asset', - href: 'http://example.com', - }, - ], - tag: validTags, - location: validLocation, - }; - const validBroadCastNoUploadedAssets = { - content: validContentNoUploadedAssets, - }; - const validReplyNoUploadedAssets = { - content: validContentNoUploadedAssets, - inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - }; - const validReaction = { - emoji: '🤌🏼', - apply: 5, - inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + ], + tag: validTags, + location: validLocation, +}; +const validBroadCastNoUploadedAssets = { + content: validContentNoUploadedAssets, +}; +const validReplyNoUploadedAssets = { + content: validContentNoUploadedAssets, + inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', +}; +const validReaction = { + emoji: randomEmoji, + apply: 5, + inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', }; const apiUrl = 'http://localhost:3001/api'; @@ -80,7 +86,7 @@ const postReaction = async (dsnpUserId, reaction) => { }; const main = async () => { - const dsnpUserId = '123'; // Replace with the desired user ID + const dsnpUserId = '1'; // Replace with the desired user ID // Example: Post broadcast const broadcastResponse = await postBroadcast(dsnpUserId, validBroadCastNoUploadedAssets); diff --git a/services/content-watcher/scripts/local-cw.sh b/services/content-watcher/scripts/local-cw.sh index bcc3c318..a6f81f06 100755 --- a/services/content-watcher/scripts/local-cw.sh +++ b/services/content-watcher/scripts/local-cw.sh @@ -26,7 +26,7 @@ npm run build # Make sure the correct set of services is running set -a -. .env.dev +. .env pm2 delete all pm2 start --cwd ${TOPDIR} ${TOPDIR}/tools/scripts/test-pm2.config.js --only mock-service diff --git a/services/content-watcher/scripts/webhook-cat.cjs b/services/content-watcher/scripts/webhook-cat.cjs new file mode 100644 index 00000000..ab03e048 --- /dev/null +++ b/services/content-watcher/scripts/webhook-cat.cjs @@ -0,0 +1,34 @@ +// Simple server that will console.log out what ever is sent to it +// Useful for seeing the output of webhooks + +const http = require('http'); + +const PORT = process.env.PORT || 6000; + +const server = http.createServer(async (req, res) => { + let body = ''; + + req.on('data', (chunk) => { + body += chunk.toString(); + }); + + req.on('end', () => { + console.log(`${new Date().toISOString()} Received ${req.method} request at ${req.url}`); + + try { + const parsedBody = JSON.parse(body); + console.log(parsedBody); + } catch (error) { + console.error('Error parsing JSON body:', error); + } + + console.log('\n'); + + res.statusCode = 200; + res.end(); + }); +}); + +server.listen(PORT, () => { + console.log(`Webhook receiver running at http://localhost:${PORT}/`); +}); From 0f5efdcc925ad7ab8892f2b5582681147b213710 Mon Sep 17 00:00:00 2001 From: Joe Caputo Date: Thu, 27 Jun 2024 16:58:35 -0400 Subject: [PATCH 130/137] feat: standardize api patterns (#82) This PR standardizes the API for certain common patterns: - Standard health endpoints: `/healthz`, `livez`, `/readyz` on root patch - All other endpoints on a `v1/