diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..dd84ea782 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/epic-template-.md b/.github/ISSUE_TEMPLATE/epic-template-.md new file mode 100644 index 000000000..e79b09fa7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/epic-template-.md @@ -0,0 +1,30 @@ +--- +name: 'Epic Template ' +about: 'Epics are milestones or groups of alike issues ' +title: "\U0001F537 [Epic] New Epic " +labels: '' +assignees: '' + +--- + +### Description +(Overview of milestone or function governed by this epic) +### Success Criteria +(Evaluate how this epic could be considered as complete and success) +### Resources +(Relevant documentation, Figma links, and other reference material) +Item 1 +Item 2 +Item 3 +```[tasklist] +### Child Issues +[ ] https://github.com/near/github-project-test/issues/1 +[ ] https://github.com/near/github-project-test/issues/2 +[ ] https://github.com/near/github-project-test/issues/3 +``` +```[tasklist] +### dependencies/blocked +[ ] https://github.com/near/github-project-test/issues/1 +[ ] https://github.com/near/github-project-test/issues/2 +[ ] https://github.com/near/github-project-test/issues/3 +``` diff --git a/.github/ISSUE_TEMPLATE/feature-request-.md b/.github/ISSUE_TEMPLATE/feature-request-.md new file mode 100644 index 000000000..17d38632d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request-.md @@ -0,0 +1,20 @@ +--- +name: 'Feature Request ' +about: Suggest an idea for this project. If this doesn't look right +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/secondary-focus-area-.md b/.github/ISSUE_TEMPLATE/secondary-focus-area-.md new file mode 100644 index 000000000..f503606ed --- /dev/null +++ b/.github/ISSUE_TEMPLATE/secondary-focus-area-.md @@ -0,0 +1,23 @@ +--- +name: 'Secondary Focus Area ' +about: This issue serves to help us propose and organize support for impactful work, + as a secondary priority to epics & planned roadmap items. If this doesn't look right +title: "\U0001F525 [Secondary Focus Area] " +labels: '' +assignees: '' + +--- + +**Motivation** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +**Open questions** diff --git a/.github/workflows/deploy-dev-widgets.yml b/.github/workflows/deploy-dev-widgets.yml index 5d0327275..19b6c7379 100644 --- a/.github/workflows/deploy-dev-widgets.yml +++ b/.github/workflows/deploy-dev-widgets.yml @@ -11,5 +11,6 @@ jobs: directory-paths: ${{vars.WIDGETS_DIRECTORY_PATHS}} deploy-account-address: ${{ vars.DEV_SIGNER_ACCOUNT_ID }} signer-public-key: ${{ vars.DEV_SIGNER_PUBLIC_KEY }} + environment: dev secrets: SIGNER_PRIVATE_KEY: ${{ secrets.DEV_SIGNER_PRIVATE_KEY }} diff --git a/.github/workflows/deploy-prod-widgets.yml b/.github/workflows/deploy-prod-widgets.yml index d847626bd..9bdbdcd54 100644 --- a/.github/workflows/deploy-prod-widgets.yml +++ b/.github/workflows/deploy-prod-widgets.yml @@ -11,5 +11,6 @@ jobs: directory-paths: ${{vars.WIDGETS_DIRECTORY_PATHS}} deploy-account-address: ${{ vars.PROD_SIGNER_ACCOUNT_ID }} signer-public-key: ${{ vars.PROD_SIGNER_PUBLIC_KEY }} + environment: mainnet secrets: SIGNER_PRIVATE_KEY: ${{ secrets.PROD_SIGNER_PRIVATE_KEY }} diff --git a/.github/workflows/deploy-widgets.yml b/.github/workflows/deploy-widgets.yml index d57dac082..2ca7f553e 100644 --- a/.github/workflows/deploy-widgets.yml +++ b/.github/workflows/deploy-widgets.yml @@ -1,4 +1,4 @@ -name: Deploy Components to Mainnet +name: Deploy Components on: workflow_call: inputs: @@ -19,6 +19,10 @@ on: required: true description: "Comma-separated paths to the directories that contain the code to be deployed" type: string + environment: + required: true + description: "mainnet or dev" + type: string secrets: SIGNER_PRIVATE_KEY: description: "Private key in `ed25519:` format for signing transaction" @@ -26,17 +30,32 @@ on: jobs: deploy-widgets: runs-on: ubuntu-latest - name: Deploy widgets to social.near (mainnet) + name: Deploy widgets to social.near env: BOS_DEPLOY_ACCOUNT_ID: ${{ inputs.deploy-account-address }} BOS_SIGNER_PUBLIC_KEY: ${{ inputs.signer-public-key }} - BOS_SIGNER_PRIVATE_KEY: ${{ secrets.SIGNER_PRIVATE_KEY }} DIRECTORY_PATHS: ${{ inputs.directory-paths }} + ENVIRONMENT: ${{inputs.environment}} + BOS_SIGNER_PRIVATE_KEY: ${{ secrets.SIGNER_PRIVATE_KEY }} steps: - name: Checkout repository uses: actions/checkout@v2 + - name: Set replacements + id: set_replacements + run: | + cd "frontend/widgets/" + echo "replacements=$(jq -r '[to_entries[] | .["find"] = "${" + .key + "}" | .["replace"] = .value | del(.key, .value)]' ../replacement.${ENVIRONMENT}.json | tr -d "\n\r")" >> $GITHUB_OUTPUT + + - name: Replace placeholders + uses: flcdrg/replace-multiple-action@v1 + with: + files: '**/*.jsx' + find: '${{ steps.set_replacements.outputs.replacements }}' + prefix: '(^|.*)' + suffix: '($|.*)' + - name: Install near-social CLI run: | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/FroVolod/bos-cli-rs/releases/download/v${{ inputs.cli-version }}/bos-cli-v${{ inputs.cli-version }}-installer.sh | sh diff --git a/.github/workflows/runner-ci.yml b/.github/workflows/runner-ci.yml new file mode 100644 index 000000000..ec8fb296b --- /dev/null +++ b/.github/workflows/runner-ci.yml @@ -0,0 +1,63 @@ +name: Runner CI + +on: + pull_request: + paths: + - "runner/**" + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: '18.x' + + - name: Install Node Dependencies + run: npm install + working-directory: ./runner + + - name: Test + run: npm test + working-directory: ./runner + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: '18.x' + + - name: Install Node Dependencies + run: npm install + working-directory: ./runner + + - name: Lint + run: npm run lint + working-directory: ./runner + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: '18.x' + + - name: Install Node Dependencies + run: npm install + working-directory: ./runner + + - name: Build + run: npm run build + working-directory: ./runner diff --git a/.gitignore b/.gitignore index 6440537ad..6a2af26a0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ redis/ *.log /indexer/blocks/ +node_modules/ diff --git a/README.md b/README.md index ef04df972..7112dd31d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ With QueryApi you can * Specify the schema for your own custom hosted database and write to it with your indexer function; * Retrieve that data through a GraphQL API. -# Table of Contents / Applications +## 🧩 Components 1. [QueryApi Coordinator](./indexer) An Indexer that tracks changes to the QueryApi registry contract. It triggers the execution of those IndexerFunctions when they match new blocks by placing messages on an SQS queue. Spawns historical processing threads when needed. @@ -13,6 +13,8 @@ when they match new blocks by placing messages on an SQS queue. Spawns historica indexer_rules_engine, storage. 2. [Indexer Runner](.indexer-js-queue-handler) Retrieves messages from the SQS queue, fetches the matching block and executes the IndexerFunction. +3. [Runner](.runner) + Retrieves messages from Redis Stream, fetching matching block and executes the IndexerFunction. 3. [IndexerFunction Editor UI](./frontend) Serves the editor UI within the dashboard widget and mediates some communication with the GraphQL DB and block server. 4. [Hasura Authentication Service](./hasura-authentication-service) @@ -21,3 +23,55 @@ indexer_rules_engine, storage. Stores IndexerFunctions, their schemas and execution parameters like start block height. 6. [Lake Block server](./block-server) Serves blocks from the S3 lake for in browser testing of IndexerFunctions. + +## 🚀 Getting Started + +The majority of the QueryApi components can be set up locally using Docker. For this purpose, a [Docker Compose file](./docker-compose.yml) has been provided. However, the local system still relies on the NEAR Mainnet, rather than running on a localnet. + +### Requirements +- [Docker](https://docs.docker.com/engine/install/) +- [Docker Compose](https://docs.docker.com/compose/install/) +- [Hasura CLI](https://hasura.io/docs/latest/hasura-cli/install-hasura-cli/) +- AWS Access Keys + +### AWS Credentials Setup +QueryApi requires AWS credentials to stream blocks from [NEAR Lake](https://github.com/near/near-lake-indexer). Credentials are exposed via the following environment variables, which can be found in the Docker Compose file: + +Runner: +- `AWS_ACCESS_KEY_ID` +- `AWS_SECRET_ACCESS_KEY` + +Coordinator: +- `LAKE_AWS_ACCESS_KEY` +- `LAKE_AWS_SECRET_ACCESS_KEY` +- `QUEUE_AWS_ACCESS_KEY` +- `QUEUE_AWS_SECRET_ACCESS_KEY` + +These should be populated with your credentials. In most cases, the same key pair can be used for all 3 sets of credentials. Just ensure the keys have permissions to access S3 for handling [Requestor Pays](https://docs.aws.amazon.com/AmazonS3/latest/userguide/RequesterPaysBuckets.html) in Near Lake. + +### Hasura Configuration +Hasura contains shared tables for e.g. logging and setting arbitrary state. These tables must be configured prior to running the entire QueryApi application. Configuration is stored in the `hasura/` directory and deployed through the Hasura CLI. + +To configure Hasura, first start it with: +```sh +docker compose up hasura-graphql --detach +``` + +And apply the configuration with: +```sh +cd ./hasura && hasura deploy +``` + +### Running QueryApi +With everything configured correctly, we can now start all components of QueryApi with: +```sh +docker compose up +``` + +### Local Configuration +- Coordinator watches the dev registry contract by default (`dev-queryapi.dataplatform.near`). To use a different contract, you can update the `REGISTRY_CONTRACT_ID` environment variable. +- Coodinator will log SQS messages rather than sending them. To use an actual Queue, you can update the `QUEUE_URL` and `START_FROM_BLOCK_QUEUE_URL` environment variables. + +### Known Issues + +It is expected to see some provisioning errors from `Runner` when starting QueryAPI for the first time. These occur when multiple indexers under the same account attempt to provision the same shared infrastructure. These should self resolve after a few seconds. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..aa4dc242d --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,94 @@ +version: "3.9" # optional since v1.27.0 +services: + + coordinator: + build: + context: ./indexer + args: + - CARGO_BUILD_MODE=debug + depends_on: + - redis + environment: + REDIS_CONNECTION_STRING: redis://redis + LAKE_AWS_ACCESS_KEY: + LAKE_AWS_SECRET_ACCESS_KEY: + QUEUE_AWS_ACCESS_KEY: + QUEUE_AWS_SECRET_ACCESS_KEY: + QUEUE_URL: MOCK + START_FROM_BLOCK_QUEUE_URL: MOCK + PORT: 9180 + REGISTRY_CONTRACT_ID: dev-queryapi.dataplatform.near + AWS_QUEUE_REGION: eu-central-1 + command: + - mainnet + - from-interruption + + runner: + build: + context: ./runner + depends_on: + - "hasura-graphql" + - "redis" + environment: + REGION: eu-central-1 + HASURA_ENDPOINT: http://hasura-graphql:8080 + HASURA_ADMIN_SECRET: myadminsecretkey + REDIS_CONNECTION_STRING: redis://redis + PGHOST: postgres + PGPORT: 5432 + PGUSER: postgres + PGPASSWORD: postgrespassword + PGDATABASE: postgres + PORT: 9180 + AWS_ACCESS_KEY_ID: + AWS_SECRET_ACCESS_KEY: + + redis: + image: redis + command: + - redis-server + - "--save 60 1" + - "--loglevel warning" + volumes: + - redis:/data + ports: + - "6379:6379" + + postgres: + image: postgres:12 + restart: always + volumes: + - postgres:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: postgrespassword + ports: + - "5432:5432" + + hasura-auth: + build: + context: ./hasura-authentication-service + ports: + - "4000:4000" + environment: + PORT: 4000 + DEFAULT_HASURA_ROLE: append + + hasura-graphql: + image: hasura/graphql-engine:latest + ports: + - "8080:8080" + depends_on: + - "postgres" + - "hasura-auth" + restart: always + environment: + HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@postgres:5432/postgres + HASURA_GRAPHQL_ENABLE_CONSOLE: "true" + HASURA_GRAPHQL_DEV_MODE: "true" + HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log + HASURA_GRAPHQL_ADMIN_SECRET: myadminsecretkey + HASURA_GRAPHQL_AUTH_HOOK: http://hasura-auth:4000/auth + +volumes: + postgres: + redis: diff --git a/frontend/README.md b/frontend/README.md index ea5bb0f32..824d47482 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -12,9 +12,11 @@ First, download the bos-loader cli by following this guide [here](https://docs.n From the root of QueryAPI Frontend repo, run the following command ```bash -yarn serve:widgets +yarn serve:widgets:local // for running local enviornment +yarn serve:widgets:dev // for running dev enviornment +yarn serve:widgets:prod // for running prod enviornment ``` -> Near.org or any other BOS gateway queries the blockchain state to pull the latest widgets code and renders it. If we would like to test our BOS widgets, we need to override the path at which the gateway (near.org) queries for the widget code. We do this using the Bos-loader tool (the underlying CLI tool used in the `yarn serve:widgets` command) which allows us to serve out widgets locally (http://127.0.0.1:3030 by default). At this point, we have served our widgets locally but have not yet told the BOS gateway (near.org) where to load our local widgets from. +> Near.org or any other BOS gateway queries the blockchain state to pull the latest widgets code and renders it. If we would like to test our BOS widgets, we need to override the path at which the gateway (near.org) queries for the widget code. We do this using the Bos-loader tool (the underlying CLI tool used in the `yarn serve:widgets:dev` command) which allows us to serve out widgets locally (http://127.0.0.1:3030 by default). ** This command replaces all keys found in `replacement.dev.json` object with the their values in the widgets directory when serving the widgets **. At this point, we have served our widgets locally but have not yet told the BOS gateway (near.org) where to load our local widgets from. **Then, Head to `near.org/flags` and enter `http://127.0.0.1:3030`** @@ -26,26 +28,7 @@ yarn serve:widgets yarn dev ``` - **Now, head to the path where the widgets are served on the BOS.** -- Prod Environment: `https://near.org/dataplatform.near/widget/QueryApi.App` -- Dev Environment: `https://near.org/dev-queryapi.dataplatform.near/widget/QueryApi.dev-App` - ---- -### Notes -> **Make sure to change your widgets code (while testing only) to point to where your local nextJS app is being served.** - -```QueryApi.App.jsx ----const EXTERNAL_APP_URL = "https://queryapi.io"; -+++const EXTERNAL_APP_URL = "http://localhost:3000"; -``` - - -> **You may need to change the accountId argument to the bos-loader CLI command in `package.json` to load from `dataplatform.near` or `dev-queryapi.dataplatform.near`. This depends on what environment you are testing for.** - -`bos-loader dev-queryapi.dataplatform.near --path widgets/src` -`bos-loader dataplatform.near --path widgets/src` - - +- Prod App: `https://near.org/dataplatform.near/widget/QueryApi.App` diff --git a/frontend/package.json b/frontend/package.json index 7a73280e4..521197bf3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,14 +4,16 @@ "private": true, "scripts": { "dev": "next dev", - "serve:widgets": "bos-loader dev-queryapi.dataplatform.near --path widgets/src", + "serve:widgets": "bos-loader dataplatform.near --path widgets/src -r replacement.mainnet.json", + "serve:widgets:local": "bos-loader dataplatform.near --path widgets/src -r replacement.local.json", + "serve:widgets:dev": "bos-loader dev-queryapi.dataplatform.near --path widgets/src -r replacement.dev.json", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { - "@graphiql/plugin-explorer": "0.3.0", "@graphiql/plugin-code-exporter": "0.3.0", + "@graphiql/plugin-explorer": "0.3.0", "@monaco-editor/react": "^4.1.3", "@near-lake/primitives": "0.1.0", "@next/font": "13.1.6", @@ -19,24 +21,25 @@ "@types/react": "18.0.28", "@types/react-dom": "18.0.10", "bootstrap": "^5.2.3", + "buffer": "^6.0.3", "eslint": "8.34.0", "eslint-config-next": "13.1.6", + "graphiql": "^2.4.1", "graphql": "^16.6.0", "near-api-js": "1.1.0", "near-social-bridge": "^1.4.1", "next": "13.1.6", + "node-sql-parser": "^4.10.0", "prettier": "^2.7.1", "prettier-plugin-sql": "^0.13.0", + "raw-loader": "^4.0.2", "react": "18.2.0", "react-bootstrap": "^2.7.2", + "react-bootstrap-icons": "^1.10.3", "react-dom": "18.2.0", "react-switch": "^7.0.0", + "regenerator-runtime": "^0.13.11", "styled-components": "^5.3.6", - "typescript": "4.9.5", - "graphiql": "^2.4.1", - "react-bootstrap-icons": "^1.10.3", - "buffer": "^6.0.3", - "raw-loader": "^4.0.2", - "regenerator-runtime": "^0.13.11" + "typescript": "4.9.5" } } diff --git a/frontend/replacement.dev.json b/frontend/replacement.dev.json new file mode 100644 index 000000000..1a233cb9d --- /dev/null +++ b/frontend/replacement.dev.json @@ -0,0 +1,6 @@ +{ + "REPL_ACCOUNT_ID": "dev-queryapi.dataplatform.near", + "REPL_GRAPHQL_ENDPOINT": "https://near-queryapi.dev.api.pagoda.co", + "REPL_EXTERNAL_APP_URL": "https://queryapi-frontend-vcqilefdcq-ew.a.run.app", + "REPL_REGISTRY_CONTRACT_ID": "dev-queryapi.dataplatform.near" +} diff --git a/frontend/replacement.local.json b/frontend/replacement.local.json new file mode 100644 index 000000000..9b8fa584e --- /dev/null +++ b/frontend/replacement.local.json @@ -0,0 +1,6 @@ +{ + "REPL_ACCOUNT_ID": "dataplatform.near", + "REPL_GRAPHQL_ENDPOINT": "https://near-queryapi.api.pagoda.co", + "REPL_EXTERNAL_APP_URL": "http://localhost:3000", + "REPL_REGISTRY_CONTRACT_ID": "queryapi.dataplatform.near" +} diff --git a/frontend/replacement.mainnet.json b/frontend/replacement.mainnet.json new file mode 100644 index 000000000..7a98e12d4 --- /dev/null +++ b/frontend/replacement.mainnet.json @@ -0,0 +1,6 @@ +{ + "REPL_ACCOUNT_ID": "dataplatform.near", + "REPL_GRAPHQL_ENDPOINT": "https://near-queryapi.api.pagoda.co", + "REPL_EXTERNAL_APP_URL": "https://queryapi-frontend-24ktefolwq-ew.a.run.app", + "REPL_REGISTRY_CONTRACT_ID": "queryapi.dataplatform.near" +} diff --git a/frontend/src/components/Editor/Editor.js b/frontend/src/components/Editor/Editor.js index 4d217e6b6..0da8175c4 100644 --- a/frontend/src/components/Editor/Editor.js +++ b/frontend/src/components/Editor/Editor.js @@ -1,10 +1,11 @@ -import React, { useEffect, useState, useMemo, useContext } from "react"; +import React, { useEffect, useState, useRef, useMemo, useContext } from "react"; import { formatSQL, formatIndexingCode, wrapCode, defaultCode, defaultSchema, + defaultSchemaTypes, } from "../../utils/formatters"; import { queryIndexerFunctionDetails } from "../../utils/queryIndexerFunction"; import { Alert } from "react-bootstrap"; @@ -20,6 +21,7 @@ import { PublishModal } from "../Modals/PublishModal"; import { ForkIndexerModal } from "../Modals/ForkIndexerModal"; import { getLatestBlockHeight } from "../../utils/getLatestBlockHeight"; import { IndexerDetailsContext } from '../../contexts/IndexerDetailsContext'; +import { PgSchemaTypeGen } from "../../utils/pgSchemaTypeGen"; const BLOCKHEIGHT_LIMIT = 3600; @@ -37,7 +39,14 @@ const Editor = ({ setAccountId, } = useContext(IndexerDetailsContext); - const DEBUG_LIST_STORAGE_KEY = `QueryAPI:debugList:${indexerDetails.accountId}#${indexerDetails.indexerName}` + const DEBUG_LIST_STORAGE_KEY = `QueryAPI:debugList:${indexerDetails.accountId + }#${indexerDetails.indexerName || "new"}`; + const SCHEMA_STORAGE_KEY = `QueryAPI:Schema:${indexerDetails.accountId}#${indexerDetails.indexerName || "new" + }`; + const SCHEMA_TYPES_STORAGE_KEY = `QueryAPI:Schema:Types:${indexerDetails.accountId}#${indexerDetails.indexerName || "new" + }`; + const CODE_STORAGE_KEY = `QueryAPI:Code:${indexerDetails.accountId}#${indexerDetails.indexerName || "new" + }`; const [error, setError] = useState(undefined); const [blockHeightError, setBlockHeightError] = useState(undefined); @@ -48,6 +57,8 @@ const Editor = ({ const [originalIndexingCode, setOriginalIndexingCode] = useState(formatIndexingCode(defaultCode)); const [indexingCode, setIndexingCode] = useState(originalIndexingCode); const [schema, setSchema] = useState(originalSQLCode); + const [schemaTypes, setSchemaTypes] = useState(defaultSchemaTypes); + const [monacoMount, setMonacoMount] = useState(false); const [heights, setHeights] = useState(localStorage.getItem(DEBUG_LIST_STORAGE_KEY) || []); @@ -67,6 +78,9 @@ const Editor = ({ }; const indexerRunner = useMemo(() => new IndexerRunner(handleLog), []); + const pgSchemaTypeGen = new PgSchemaTypeGen(); + const disposableRef = useRef(null); + useEffect(() => { if (!indexerDetails.code || !indexerDetails.schema) return const { formattedCode, formattedSchema } = reformatAll(indexerDetails.code, indexerDetails.schema) @@ -76,25 +90,82 @@ const Editor = ({ setSchema(formattedSchema) }, [indexerDetails.code, indexerDetails.schema]); + useEffect(() => { + + const savedSchema = localStorage.getItem(SCHEMA_STORAGE_KEY); + const savedCode = localStorage.getItem(CODE_STORAGE_KEY); + + if (savedSchema) { + setSchema(savedSchema); + try { + setSchemaTypes(pgSchemaTypeGen.generateTypes(savedSchema)); + setError(() => undefined); + } catch (error) { + handleCodeGenError(error); + } + + } + if (savedCode) setIndexingCode(savedCode); + }, [indexerDetails.accountId, indexerDetails.indexerName]); + + useEffect(() => { + localStorage.setItem(SCHEMA_STORAGE_KEY, schema); + localStorage.setItem(CODE_STORAGE_KEY, indexingCode); + }, [schema, indexingCode]); + + useEffect(() => { + localStorage.setItem(SCHEMA_TYPES_STORAGE_KEY, schemaTypes); + attachTypesToMonaco(); + }, [schemaTypes, monacoMount]); + const requestLatestBlockHeight = async () => { const blockHeight = getLatestBlockHeight() return blockHeight } useEffect(() => { - if (selectedTab === "playground") { - setFileName("GraphiQL"); + if (fileName === "indexingLogic.js") { + try { + setSchemaTypes(pgSchemaTypeGen.generateTypes(schema)); + setError(() => undefined); + } catch (error) { + handleCodeGenError(error); + } } - }, [selectedTab]); + }, [fileName]); useEffect(() => { localStorage.setItem(DEBUG_LIST_STORAGE_KEY, heights); }, [heights]); + const attachTypesToMonaco = () => { + // If types has been added already, dispose of them first + if (disposableRef.current) { + disposableRef.current.dispose(); + disposableRef.current = null; + } + + if (window.monaco) { // Check if monaco is loaded + // Add generated types to monaco and store disposable to clear them later + const newDisposable = monaco.languages.typescript.typescriptDefaults.addExtraLib(schemaTypes); + if (newDisposable != null) { + console.log("Types successfully imported to Editor"); + } + disposableRef.current = newDisposable; + } + } + const checkSQLSchemaFormatting = () => { try { let formatted_sql = formatSQL(schema); let formatted_schema = formatted_sql; + try { + pgSchemaTypeGen.generateTypes(formatted_sql); // Sanity check + } catch (error) { + handleCodeGenError(error); + return undefined; + } + return formatted_schema; } catch (error) { console.log("error", error); @@ -152,8 +223,9 @@ const Editor = ({ const handleReload = async () => { if (isCreateNewIndexer) { setShowResetCodeModel(false); - setIndexingCode((formatIndexingCode(indexerDetails.code))); - setSchema(formatSQL(indexerDetails.schema)) + setIndexingCode(originalIndexingCode); + setSchema(originalSQLCode); + setSchemaTypes(defaultSchemaTypes); return; } @@ -161,6 +233,7 @@ const Editor = ({ if (data == null) { setIndexingCode(defaultCode); setSchema(defaultSchema); + setSchemaTypes(defaultSchemaTypes); setError(() => onLoadErrorText); } else { try { @@ -235,6 +308,22 @@ const Editor = ({ }); }; + function handleCodeGen() { + try { + setSchemaTypes(pgSchemaTypeGen.generateTypes(schema)); + attachTypesToMonaco(); // Just in case schema types have been updated but weren't added to monaco + setError(() => undefined); + } catch (error) { + handleCodeGenError(error); + } + } + + const handleCodeGenError = (error) => { + console.error("Error generating types for saved schema.\n", error); + const errorMessage = "Oh snap! We could not generate types for your SQL schema. Make sure it is proper SQL DDL." + setError(() => errorMessage); + }; + async function handleFormating() { await reformat(indexingCode, schema); } @@ -256,15 +345,17 @@ const Editor = ({ `${primitives}}`, "file:///node_modules/@near-lake/primitives/index.d.ts" ); + setMonacoMount(true); } async function executeIndexerFunction(option = "latest", startingBlockHeight = null) { setIsExecutingIndexerFunction(() => true) + const schemaName = indexerDetails.accountId.concat("_", indexerDetails.indexerName).replace(/[^a-zA-Z0-9]/g, '_'); switch (option) { case "debugList": - await indexerRunner.executeIndexerFunctionOnHeights(heights, indexingCode, option) + await indexerRunner.executeIndexerFunctionOnHeights(heights, indexingCode, schema, schemaName, option) break case "specific": if (startingBlockHeight === null && Number(startingBlockHeight) === 0) { @@ -272,11 +363,11 @@ const Editor = ({ break } - await indexerRunner.start(startingBlockHeight, indexingCode, option) + await indexerRunner.start(startingBlockHeight, indexingCode, schema, schemaName, option) break case "latest": const latestHeight = await requestLatestBlockHeight() - if (latestHeight) await indexerRunner.start(latestHeight - 10, indexingCode, option) + if (latestHeight) await indexerRunner.start(latestHeight - 10, indexingCode, schema, schemaName, option) } setIsExecutingIndexerFunction(() => false) } @@ -292,6 +383,7 @@ const Editor = ({ > + + Generate Types} + > + + {(!isUserIndexer && !isCreateNewIndexer) ? ( { const response = await fetch(HASURA_ENDPOINT, { diff --git a/frontend/src/utils/formatters.js b/frontend/src/utils/formatters.js index 099863723..6c312f7d8 100644 --- a/frontend/src/utils/formatters.js +++ b/frontend/src/utils/formatters.js @@ -10,13 +10,14 @@ let wrap_code = (code) => `import {Block} from "@near-lake/primitives" /** * getBlock(block, context) applies your custom logic to a Block on Near and commits the data to a database. + * context is a global variable that contains helper methods. + * context.db is a subfield which contains helper methods to interact with your database. * * Learn more about indexers here: https://docs.near.org/concepts/advanced/indexers * - * @param {block} Block - A Near Protocol Block - * @param {context} - A set of helper methods to retrieve and commit state + * @param {block} Block - A Near Protocol Block */ -async function getBlock(block: Block, context) { +async function getBlock(block: Block) { ${code} }`; @@ -54,3 +55,33 @@ export const defaultCode = formatIndexingCode(wrapCode( export const defaultSchema = ` CREATE TABLE "indexer_storage" ("function_name" TEXT NOT NULL, "key_name" TEXT NOT NULL, "value" TEXT NOT NULL, PRIMARY KEY ("function_name", "key_name")) `; + +export const defaultSchemaTypes = `declare interface IndexerStorageItem { + function_name?: string; + key_name?: string; + value?: string; +} + +declare interface IndexerStorageInput { + function_name: string; + key_name: string; + value: string; +} + +declare const context: { + + graphql: (operation, variables) => Promise, + set: (key, value) => Promise, + log: (...log) => Promise, + fetchFromSocialApi: (path, options) => Promise, + db: { + IndexerStorage: { + insert: (objects: IndexerStorageInput | IndexerStorageInput[]) => Promise; + select: (object: IndexerStorageItem, limit = null) => Promise; + update: (whereObj: IndexerStorageItem, updateObj: IndexerStorageItem) => Promise; + upsert: (objects: IndexerStorageInput | IndexerStorageInput[], conflictColumns: IndexerStorageItem, updateColumns: IndexerStorageItem) => Promise; + delete: (object: IndexerStorageInput) => Promise; + }, + } +}; +` \ No newline at end of file diff --git a/frontend/src/utils/formatters.test.js b/frontend/src/utils/formatters.test.js index a35825e4a..258070edb 100644 --- a/frontend/src/utils/formatters.test.js +++ b/frontend/src/utils/formatters.test.js @@ -38,14 +38,15 @@ const expectedOutput3 = `import { Block } from "@near-lake/primitives"; */ /** - * getBlock(block, context) applies your custom logic to a Block on Near and commits the data to a database. - * + * getBlock(block, context) applies your custom logic to a Block on Near and commits the data to a database. + * context is a global variable that contains helper methods. + * context.db is a subfield which contains helper methods to interact with your database. + * * Learn more about indexers here: https://docs.near.org/concepts/advanced/indexers - * + * * @param {block} Block - A Near Protocol Block - * @param {context} - A set of helper methods to retrieve and commit state */ -async function getBlock(block: Block, context) { +async function getBlock(block: Block) { const h = block.header().height; console.log("About to write demo_blockheight", h); await context.set("demo_height", h); diff --git a/frontend/src/utils/indexerRunner.js b/frontend/src/utils/indexerRunner.js index 123226bbb..05320d3c3 100644 --- a/frontend/src/utils/indexerRunner.js +++ b/frontend/src/utils/indexerRunner.js @@ -1,6 +1,7 @@ import { Block } from "@near-lake/primitives"; import { Buffer } from "buffer"; import { fetchBlockDetails } from "./fetchBlock"; +import { PgSchemaTypeGen } from "./pgSchemaTypeGen"; global.Buffer = Buffer; export default class IndexerRunner { @@ -8,9 +9,10 @@ export default class IndexerRunner { this.handleLog = handleLog; this.currentHeight = 0; this.shouldStop = false; + this.pgSchemaTypeGen = new PgSchemaTypeGen(); } - async start(startingHeight, indexingCode, option) { + async start(startingHeight, indexingCode, schema, schemaName, option) { this.currentHeight = startingHeight; this.shouldStop = false; console.clear() @@ -32,7 +34,7 @@ export default class IndexerRunner { this.stop() } if (blockDetails) { - await this.executeIndexerFunction(this.currentHeight, blockDetails, indexingCode); + await this.executeIndexerFunction(this.currentHeight, blockDetails, indexingCode, schema, schemaName); this.currentHeight++; await this.delay(1000); } @@ -50,7 +52,7 @@ export default class IndexerRunner { return new Promise((resolve) => setTimeout(resolve, ms)); } - async executeIndexerFunction(height, blockDetails, indexingCode) { + async executeIndexerFunction(height, blockDetails, indexingCode, schema, schemaName) { let innerCode = indexingCode.match(/getBlock\s*\([^)]*\)\s*{([\s\S]*)}/)[1]; if (blockDetails) { const block = Block.fromStreamerMessage(blockDetails); @@ -59,11 +61,11 @@ export default class IndexerRunner { block.events() console.log(block) - await this.runFunction(blockDetails, height, innerCode); + await this.runFunction(blockDetails, height, innerCode, schemaName, schema); } } - async executeIndexerFunctionOnHeights(heights, indexingCode) { + async executeIndexerFunctionOnHeights(heights, indexingCode, schema, schemaName) { console.clear() console.group('%c Welcome! Lets test your indexing logic on some Near Blocks!', 'color: white; background-color: navy; padding: 5px;'); if (heights.length === 0) { @@ -80,14 +82,14 @@ export default class IndexerRunner { console.log(error) } console.time('Indexing Execution Complete') - this.executeIndexerFunction(height, blockDetails, indexingCode) + this.executeIndexerFunction(height, blockDetails, indexingCode, schema, schemaName) console.timeEnd('Indexing Execution Complete') console.groupEnd() } console.groupEnd() } - async runFunction(streamerMessage, blockHeight, indexerCode) { + async runFunction(streamerMessage, blockHeight, indexerCode, schemaName, schema) { const innerCodeWithBlockHelper = ` const block = Block.fromStreamerMessage(streamerMessage); @@ -125,21 +127,20 @@ export default class IndexerRunner { "", () => { let operationType, operationName - const match = query.match(/(query|mutation)\s+(\w+)\s*(\(.*?\))?\s*\{([\s\S]*)\}/); - if (match) { - operationType = match[1]; - operationName = match[2]; - } - - console.group(`Executing GraphQL ${operationType}: (${operationName})`); - if (operationType === 'mutation') console.log('%c Mutations in debug mode do not alter the database', 'color: black; background-color: yellow; padding: 5px;'); - console.group(`Data passed to ${operationType}`); - console.dir(mutationData); - console.groupEnd(); - console.group(`Data returned by ${operationType}`); - console.log({}) - console.groupEnd(); - console.groupEnd(); + const match = query.match(/(query|mutation)\s+(\w+)\s*(\(.*?\))?\s*\{([\s\S]*)\}/); + if (match) { + operationType = match[1]; + operationName = match[2]; + } + console.group(`Executing GraphQL ${operationType}: (${operationName})`); + if (operationType === 'mutation') console.log('%c Mutations in debug mode do not alter the database', 'color: black; background-color: yellow; padding: 5px;'); + console.group(`Data passed to ${operationType}`); + console.dir(mutationData); + console.groupEnd(); + console.group(`Data returned by ${operationType}`); + console.log({}) + console.groupEnd(); + console.groupEnd(); } ); return {}; @@ -147,11 +148,75 @@ export default class IndexerRunner { log: async (message) => { this.handleLog(blockHeight, message); }, + db: this.buildDatabaseContext(blockHeight, schemaName, schema) }; wrappedFunction(Block, streamerMessage, context); } + buildDatabaseContext (blockHeight, schemaName, schema) { + try { + const tables = this.pgSchemaTypeGen.getTableNames(schema); + const sanitizedTableNames = new Set(); + + // Generate and collect methods for each table name + const result = tables.reduce((prev, tableName) => { + // Generate sanitized table name and ensure no conflict + const sanitizedTableName = this.pgSchemaTypeGen.sanitizeTableName(tableName); + if (sanitizedTableNames.has(sanitizedTableName)) { + throw new Error(`Table '${tableName}' has the same name as another table in the generated types. Special characters are removed to generate context.db methods. Please rename the table.`); + } else { + sanitizedTableNames.add(sanitizedTableName); + } + + // Generate context.db methods for table + const funcForTable = { + [`${sanitizedTableName}`]: { + insert: async (objects) => await this.dbOperationLog(blockHeight, + `Inserting the following objects into table ${sanitizedTableName} on schema ${schemaName}`, + objects), + + select: async (object, limit = null) => await this.dbOperationLog(blockHeight, + `Selecting objects with the following values from table ${sanitizedTableName} on schema ${schemaName} with ${limit === null ? 'no' : limit} limit`, + object), + + update: async (whereObj, updateObj) => await this.dbOperationLog(blockHeight, + `Updating objects that match the specified fields with the following values in table ${sanitizedTableName} on schema ${schemaName}`, + {matchingFields: whereObj, fieldsToUpdate: updateObj}), + + upsert: async (objects, conflictColumns, updateColumns) => await this.dbOperationLog(blockHeight, + `Inserting the following objects into table ${sanitizedTableName} on schema ${schemaName}. Conflict on the specified columns will update values in the specified columns`, + {insertObjects: objects, conflictColumns: conflictColumns.join(', '), updateColumns: updateColumns.join(', ')}), + + delete: async (object) => await this.dbOperationLog(blockHeight, + `Deleting objects which match the following object's values from table ${sanitizedTableName} on schema ${schemaName}`, object) + } + }; + + return { + ...prev, + ...funcForTable + }; + }, {}); + return result; + } catch (error) { + console.warn('Caught error when generating context.db methods. Building no functions. You can still use other context object methods.\n', error); + } + } + + dbOperationLog(blockHeight, logMessage, data) { + this.handleLog( + blockHeight, + "", + () => { + console.group(logMessage); + console.log(data); + console.groupEnd(); + } + ); + return {}; + } + // deprecated replaceNewLines(code) { return code.replace(/\\n/g, "\n").replace(/\\"/g, '"'); diff --git a/frontend/src/utils/pgSchemaTypeGen.js b/frontend/src/utils/pgSchemaTypeGen.js new file mode 100644 index 000000000..8db5f2fb7 --- /dev/null +++ b/frontend/src/utils/pgSchemaTypeGen.js @@ -0,0 +1,268 @@ +import { Parser } from "node-sql-parser"; + +export class PgSchemaTypeGen { + constructor() { + this.parser = new Parser(); + this.tables = new Set(); + } + + sanitizeTableName(tableName) { + // Convert to PascalCase + let pascalCaseTableName = tableName + // Replace special characters with underscores + .replace(/[^a-zA-Z0-9_]/g, '_') + // Makes first letter and any letters following an underscore upper case + .replace(/^([a-zA-Z])|_([a-zA-Z])/g, (match) => match.toUpperCase()) + // Removes all underscores + .replace(/_/g, ''); + + // Add underscore if first character is a number + if (/^[0-9]/.test(pascalCaseTableName)) { + pascalCaseTableName = '_' + pascalCaseTableName; + } + + return pascalCaseTableName; + } + + getTableNames (schema) { + let schemaSyntaxTree = this.parser.astify(schema, { database: 'Postgresql' }); + schemaSyntaxTree = Array.isArray(schemaSyntaxTree) ? schemaSyntaxTree : [schemaSyntaxTree]; // Ensure iterable + const tableNames = new Set(); + + // Collect all table names from schema AST, throw error if duplicate table names exist + for (const statement of schemaSyntaxTree) { + if (statement.type === 'create' && statement.keyword === 'table' && statement.table !== undefined) { + const tableName = statement.table[0].table; + + if (tableNames.has(tableName)) { + throw new Error(`Table ${tableName} already exists in schema. Table names must be unique. Quotes are not allowed as a differentiator between table names.`); + } + + tableNames.add(tableName); + } + } + + // Ensure schema is not empty + if (tableNames.size === 0) { + throw new Error('Schema does not have any tables. There should be at least one table.'); + } + + const tableNamesArray = Array.from(tableNames); + return Array.from(tableNamesArray); + } + + generateTypes(sqlSchema) { + const schemaSyntaxTree = this.parser.astify(sqlSchema, { database: "Postgresql" }); + const dbSchema = {}; + + // Process each statement in the schema + for (const statement of schemaSyntaxTree) { + if (statement.type === "create" && statement.keyword === "table") { + // Process CREATE TABLE statements + const tableName = statement.table[0].table; + if (dbSchema.hasOwnProperty(tableName)) { + throw new Error(`Table ${tableName} already exists in schema. Table names must be unique. Quotes are not allowed as a differentiator between table names.`); + } + + let columns = {}; + for (const columnSpec of statement.create_definitions) { + if (columnSpec.hasOwnProperty("column") && columnSpec.hasOwnProperty("definition")) { + // New Column + this.addColumn(columnSpec, columns); + } else if (columnSpec.hasOwnProperty("constraint") && columnSpec.constraint_type == "primary key") { + // Constraint on existing column + for (const foreignKeyDef of columnSpec.definition) { + columns[foreignKeyDef.column].nullable = false; + } + } + } + dbSchema[tableName] = columns; + } else if (statement.type === "alter") { + // Process ALTER TABLE statements + const tableName = statement.table[0].table; + for (const alterSpec of statement.expr) { + switch (alterSpec.action) { + case "add": + switch (alterSpec.resource) { + case "column": // Add column to table + this.addColumn(alterSpec, dbSchema[tableName]); + break; + case "constraint": // Add constraint to column(s) (Only PRIMARY KEY constraint impacts output types) + const newConstraint = alterSpec.create_definitions; + if (newConstraint.constraint_type == "primary key") { + for (const foreignKeyDef of newConstraint.definition) { + dbSchema[tableName][foreignKeyDef.column].nullable = false; + } + } + break; + } + break; + case "drop": // Can only drop column for now + delete dbSchema[tableName][alterSpec.column.column]; + break; + } + } + } + } + + const tsTypes = this.generateTypeScriptDefinitions(dbSchema); + console.log(`Types successfully generated`); + return tsTypes; + } + + addColumn(columnDef, columns) { + const columnName = columnDef.column.column; + const columnType = this.getTypescriptType(columnDef.definition.dataType); + const nullable = this.getNullableStatus(columnDef); + const required = this.getRequiredStatus(columnDef, nullable); + if (columns.hasOwnProperty(columnName)) { + console.warn(`Column ${columnName} already exists in table. Skipping.`); + return; + } + columns[columnName] = { + type: columnType, + nullable: nullable, + required: required, + }; + } + + getNullableStatus(columnDef) { + const isPrimaryKey = + columnDef.hasOwnProperty("unique_or_primary") && + columnDef.unique_or_primary == "primary key"; + const isNullable = + columnDef.hasOwnProperty("nullable") && + columnDef.nullable.value == "not null"; + return isPrimaryKey || isNullable ? false : true; + } + + getRequiredStatus(columnDef, nullable) { + const hasDefaultValue = + columnDef.hasOwnProperty("default_val") && columnDef.default_val != null; + const isSerial = columnDef.definition.dataType + .toLowerCase() + .includes("serial"); + return hasDefaultValue || isSerial || nullable ? false : true; + } + + generateTypeScriptDefinitions(schema) { + const tableList = new Set(); + let tsDefinitions = ""; + let contextObject = `declare const context: { + graphql: (operation, variables) => Promise, + set: (key, value) => Promise, + log: (...log) => Promise, + fetchFromSocialApi: (path, options) => Promise, + db: {`; + + // Process each table + for (const [tableName, columns] of Object.entries(schema)) { + let itemDefinition = ""; + let inputDefinition = ""; + const sanitizedTableName = this.sanitizeTableName(tableName); + if (tableList.has(sanitizedTableName)) { + throw new Error(`Table '${tableName}' has the same name as another table in the generated types. Special characters are removed to generate context.db methods. Please rename the table.`); + } + tableList.add(sanitizedTableName); + // Create interfaces for strongly typed input and row item + itemDefinition += `declare interface ${sanitizedTableName}Item {\n`; + inputDefinition += `declare interface ${sanitizedTableName}Input {\n`; + for (const [columnName, columnDetails] of Object.entries(columns)) { + let tsType = columnDetails.nullable ? columnDetails.type + " | null" : columnDetails.type; + const optional = columnDetails.required ? "" : "?"; + itemDefinition += ` ${columnName}?: ${tsType};\n`; // Item fields are always optional + inputDefinition += ` ${columnName}${optional}: ${tsType};\n`; + } + itemDefinition += "}\n\n"; + inputDefinition += "}\n\n"; + + // Create type containing column names to be used as a replacement for string[]. + const columnNamesDef = `type ${sanitizedTableName}Columns = "${Object.keys(columns).join('" | "')}";\n\n`; + + // Add generated types to definitions + tsDefinitions += itemDefinition + inputDefinition + columnNamesDef; + + // Create context object with correctly formatted methods. Name, input, and output should match actual implementation + contextObject += ` + ${sanitizedTableName}: { + insert: (objectsToInsert: ${sanitizedTableName}Input | ${sanitizedTableName}Input[]) => Promise<${sanitizedTableName}Item[]>; + select: (filterObj: ${sanitizedTableName}Item, limit = null) => Promise<${sanitizedTableName}Item[]>; + update: (filterObj: ${sanitizedTableName}Item, updateObj: ${sanitizedTableName}Item) => Promise<${sanitizedTableName}Item[]>; + upsert: (objectsToInsert: ${sanitizedTableName}Input | ${sanitizedTableName}Input[], conflictColumns: ${sanitizedTableName}Columns[], updateColumns: ${sanitizedTableName}Columns[]) => Promise<${sanitizedTableName}Item[]>; + delete: (filterObj: ${sanitizedTableName}Item) => Promise<${sanitizedTableName}Item[]>; + },`; + } + + contextObject += '\n }\n};' + this.tableList = tableList; + + return tsDefinitions + contextObject; + } + + getTypescriptType(pgType) { + switch (pgType.toLowerCase()) { + // Numeric types + case "smallint": + case "integer": + case "bigint": + case "decimal": + case "numeric": + case "real": + case "double precision": + case "serial": + case "bigserial": + return "number"; + + // Monetary types + case "money": + return "number"; + + // Character types + case "character varying": + case "varchar": + case "character": + case "char": + case "text": + return "string"; + + // Binary data types + case "bytea": + return "Buffer"; + + // Boolean type + case "boolean": + return "boolean"; + + // Date/Time types + case "timestamp": + case "timestamp without time zone": + case "timestamp with time zone": + case "date": + case "time": + case "time without time zone": + case "time with time zone": + case "interval": + return "Date"; + + // UUID type + case "uuid": + return "string"; + + // JSON types + case "json": + case "jsonb": + return "any"; + + // Arrays + case "integer[]": + return "number[]"; + + case "text[]": + return "string[]"; + + // Others + default: + return "any"; + } + } +} \ No newline at end of file diff --git a/frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Comment.jsx b/frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Comment.jsx index 937197f75..14100679f 100644 --- a/frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Comment.jsx +++ b/frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Comment.jsx @@ -6,6 +6,7 @@ const blockHeight = props.blockHeight === "now" ? "now" : parseInt(props.blockHeight); State.init({ + hasBeenFlagged: false, content: JSON.parse(props.content) ?? undefined, notifyAccountId: undefined, }); @@ -122,6 +123,14 @@ const Actions = styled.div` margin: -6px -6px 6px; `; +if (state.hasBeenFlagged) { + return ( +
+ This content has been flagged for moderation +
+ ); +} + return (
@@ -196,6 +205,19 @@ return ( url: commentUrl, }} /> + { + State.update({ hasBeenFlagged: true }); + }, + }} + /> )} diff --git a/frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Post.jsx b/frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Post.jsx index 4e7673a31..ee3f45159 100644 --- a/frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Post.jsx +++ b/frontend/widgets/examples/feed/src/QueryApi.Examples.Feed.Post.jsx @@ -7,8 +7,8 @@ const blockHeight = // const subscribe = !!props.subscribe; const notifyAccountId = accountId; const postUrl = `https://alpha.near.org/#/${APP_OWNER}/widget/QueryApi.Examples.Feed.PostPage?accountId=${accountId}&blockHeight=${blockHeight}`; - State.init({ + hasBeenFlagged: false, postExists: true, comments: props.comments ?? undefined, content: JSON.parse(props.content) ?? undefined, @@ -185,7 +185,13 @@ const renderComment = (a) => { ); }; - +if (state.hasBeenFlagged) { + return ( +
+ This content has been flagged for moderation +
+ ); +} const renderedComments = state.comments.map(renderComment); return ( @@ -263,9 +269,17 @@ return ( url: postUrl, }} /> + { + State.update({ hasBeenFlagged: true }); + }, + }} + /> )} - {state.showReply && (
@@ -448,16 +431,12 @@ return ( {state.activeTab === "create-new-indexer" && (
@@ -474,18 +453,14 @@ return ( ))} {indexerView( selected_accountId ?? state.indexers[0].accountId, - selected_indexerName ?? state.indexers[0].indexerName, + selected_indexerName ?? state.indexers[0].indexerName )} @@ -505,17 +480,13 @@ return (

{`${state.indexers[0].accountId}/${state.indexers[0].indexerName}`}

))} @@ -529,16 +500,12 @@ return (

{`${state.indexers[0].accountId}/${state.indexers[0].indexerName}`}

))} diff --git a/frontend/widgets/src/QueryApi.Editor.jsx b/frontend/widgets/src/QueryApi.Editor.jsx index 147080a90..8deadca58 100644 --- a/frontend/widgets/src/QueryApi.Editor.jsx +++ b/frontend/widgets/src/QueryApi.Editor.jsx @@ -1,12 +1,7 @@ const path = props.path || "query-api-editor"; const tab = props.tab || ""; -const REGISTRY_CONTRACT_ID = - props.REGISTRY_CONTRACT_ID || "queryapi.dataplatform.near"; let accountId = props.accountId || context.accountId; -let externalAppUrl = - props.EXTERNAL_APP_URL || "https://queryapi-frontend-24ktefolwq-ew.a.run.app"; -externalAppUrl += `/${path}?accountId=${accountId}`; -// let externalAppUrl = `http://localhost:3000/${path}?accountId=${accountId}`; +let externalAppUrl = `${REPL_EXTERNAL_APP_URL}/${path}?accountId=${accountId}`; if (props.indexerName) { externalAppUrl += `&indexerName=${props.indexerName}`; @@ -30,7 +25,7 @@ const registerFunctionHandler = (request, response) => { const jsonFilter = `{"indexer_rule_kind":"Action","matching_rule":{"rule":"ACTION_ANY","affected_account_id":"${contractFilter || "social.near"}","status":"SUCCESS"}}` Near.call( - REGISTRY_CONTRACT_ID, + `${REPL_REGISTRY_CONTRACT_ID}`, "register_indexer_function", { function_name: indexerName, @@ -47,7 +42,7 @@ let deleteIndexer = (request) => { const { indexerName } = request.payload; const gas = 200000000000000; Near.call( - REGISTRY_CONTRACT_ID, + `${REPL_REGISTRY_CONTRACT_ID}`, "remove_indexer_function", { function_name: indexerName, @@ -55,6 +50,7 @@ let deleteIndexer = (request) => { gas ); }; + /** * Request Handlers here */ diff --git a/frontend/widgets/src/QueryApi.IndexerCard.jsx b/frontend/widgets/src/QueryApi.IndexerCard.jsx index 431973dcb..f64545ba3 100644 --- a/frontend/widgets/src/QueryApi.IndexerCard.jsx +++ b/frontend/widgets/src/QueryApi.IndexerCard.jsx @@ -1,14 +1,8 @@ const accountId = props.accountId || context.accountId; const indexerName = props.indexerName; -const GRAPHQL_ENDPOINT = - props.GRAPHQL_ENDPOINT || - "https://queryapi-hasura-graphql-24ktefolwq-ew.a.run.app"; -const APP_OWNER = props.APP_OWNER || "dataplatform.near"; -const appPath = props.appPath || "App"; -const editUrl = `https://near.org/#/${APP_OWNER}/widget/QueryApi.${appPath}?selectedIndexerPath=${accountId}/${indexerName}&view=editor-window`; -const statusUrl = `https://near.org/#/${APP_OWNER}/widget/QueryApi.${appPath}?selectedIndexerPath=${accountId}/${indexerName}&view=indexer-status`; -// const playgroundLink = `https://near.org/#/${APP_OWNER}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=editor-window&tab=playground`; -const playgroundLink = `https://cloud.hasura.io/public/graphiql?endpoint=${GRAPHQL_ENDPOINT}/v1/graphql&header=x-hasura-role%3A${accountId.replaceAll( +const editUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=editor-window`; +const statusUrl = `https://near.org/#/${REPL_ACCOUNT_ID}/widget/QueryApi.App?selectedIndexerPath=${accountId}/${indexerName}&view=indexer-status`; +const playgroundLink = `https://cloud.hasura.io/public/graphiql?endpoint=${REPL_GRAPHQL_ENDPOINT}/v1/graphql&header=x-hasura-role%3A${accountId.replaceAll( ".", "_" )}`; diff --git a/frontend/widgets/src/QueryApi.IndexerExplorer.jsx b/frontend/widgets/src/QueryApi.IndexerExplorer.jsx index 62afe4def..8ac6e079e 100644 --- a/frontend/widgets/src/QueryApi.IndexerExplorer.jsx +++ b/frontend/widgets/src/QueryApi.IndexerExplorer.jsx @@ -1,10 +1,4 @@ const limitPerPage = 5; -const REGISTRY_CONTRACT_ID = - props.REGISTRY_CONTRACT_ID || "queryapi.dataplatform.near"; -let APP_OWNER = props.APP_OWNER || "dev-queryapi.dataplatform.near"; -const GRAPHQL_ENDPOINT = - props.GRAPHQL_ENDPOINT || - "https://queryapi-hasura-graphql-24ktefolwq-ew.a.run.app"; let totalIndexers = 0; const accountId = context.accountId; State.init({ @@ -21,7 +15,7 @@ if (props.tab && props.tab !== state.selectedTab) { }); } -Near.asyncView(REGISTRY_CONTRACT_ID, "list_indexer_functions").then((data) => { +Near.asyncView(`${REPL_REGISTRY_CONTRACT_ID}`, "list_indexer_functions").then((data) => { const indexers = []; const total_indexers = 0; Object.keys(data.All).forEach((accountId) => { @@ -242,13 +236,10 @@ return ( {state.all_indexers.map((indexer, i) => ( @@ -275,13 +266,10 @@ return ( {state.my_indexers.map((indexer, i) => ( diff --git a/frontend/widgets/src/QueryApi.IndexerStatus.jsx b/frontend/widgets/src/QueryApi.IndexerStatus.jsx index 492f650e2..4def76129 100644 --- a/frontend/widgets/src/QueryApi.IndexerStatus.jsx +++ b/frontend/widgets/src/QueryApi.IndexerStatus.jsx @@ -1,11 +1,9 @@ //props indexer_name const indexer_name = props.indexer_name; -const GRAPHQL_ENDPOINT = - props.GRAPHQL_ENDPOINT || - "https://queryapi-hasura-graphql-24ktefolwq-ew.a.run.app"; const LIMIT = 20; const accountId = props.accountId || context.accountId; + const H2 = styled.h2` font-size: 19px; line-height: 22px; @@ -110,7 +108,7 @@ State.init({ }); function fetchGraphQL(operationsDoc, operationName, variables) { - return asyncFetch(`${GRAPHQL_ENDPOINT}/v1/graphql`, { + return asyncFetch(`${REPL_GRAPHQL_ENDPOINT}/v1/graphql`, { method: "POST", body: JSON.stringify({ query: operationsDoc, @@ -121,7 +119,7 @@ function fetchGraphQL(operationsDoc, operationName, variables) { } const createGraphQLLink = () => { - const queryLink = `https://cloud.hasura.io/public/graphiql?endpoint=${GRAPHQL_ENDPOINT}/v1/graphql&query=query+IndexerQuery+%7B%0A++indexer_state%28where%3A+%7Bfunction_name%3A+%7B_eq%3A+%22function_placeholder%22%7D%7D%29+%7B%0A++++function_name%0A++++current_block_height%0A++%7D%0A++indexer_log_entries%28%0A++++where%3A+%7Bfunction_name%3A+%7B_eq%3A+%22function_placeholder%22%7D%7D%0A++++order_by%3A+%7B+timestamp%3A+desc%7D%0A++%29+%7B%0A++++function_name%0A++++id%0A++++message%0A++++timestamp%0A++%7D%0A%7D%0A`; + const queryLink = `https://cloud.hasura.io/public/graphiql?endpoint=${REPL_GRAPHQL_ENDPOINT}/v1/graphql&query=query+IndexerQuery+%7B%0A++indexer_state%28where%3A+%7Bfunction_name%3A+%7B_eq%3A+%22function_placeholder%22%7D%7D%29+%7B%0A++++function_name%0A++++current_block_height%0A++%7D%0A++indexer_log_entries%28%0A++++where%3A+%7Bfunction_name%3A+%7B_eq%3A+%22function_placeholder%22%7D%7D%0A++++order_by%3A+%7B+timestamp%3A+desc%7D%0A++%29+%7B%0A++++function_name%0A++++id%0A++++message%0A++++timestamp%0A++%7D%0A%7D%0A`; return queryLink.replaceAll( "function_placeholder", `${accountId}/${indexer_name}` diff --git a/frontend/widgets/src/QueryApi.dev-App.jsx b/frontend/widgets/src/QueryApi.dev-App.jsx deleted file mode 100644 index 224ab9d0d..000000000 --- a/frontend/widgets/src/QueryApi.dev-App.jsx +++ /dev/null @@ -1,25 +0,0 @@ -const GRAPHQL_ENDPOINT = "https://queryapi-hasura-graphql-vcqilefdcq-ew.a.run.app"; -const APP_OWNER = "dev-queryapi.dataplatform.near"; -const EXTERNAL_APP_URL = "https://queryapi-frontend-vcqilefdcq-ew.a.run.app"; -const REGISTRY_CONTRACT_ID = "dev-queryapi.dataplatform.near"; -const view = props.view; -const path = props.path; -const tab = props.tab; -const selectedIndexerPath = props.selectedIndexerPath; - -return ( - -); diff --git a/frontend/widgets/src/QueryApi.dev-App.metadata.json b/frontend/widgets/src/QueryApi.dev-App.metadata.json deleted file mode 100644 index bfbdf4a96..000000000 --- a/frontend/widgets/src/QueryApi.dev-App.metadata.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "description": "Entrypoint to Near QueryAPI's development widget which allows you to seamlessly create, manage, and discover new indexers", - "image": { - }, - "name": "QueryAPI Development", - "tags": { - "development": "" - } -} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 290484472..d10e66f86 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -86,14 +86,14 @@ "@babel/runtime@^7.0.0": version "7.22.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.3.tgz" integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ== dependencies: regenerator-runtime "^0.13.11" "@babel/runtime@^7.12.13": version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz" integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw== dependencies: regenerator-runtime "^0.13.11" @@ -176,9 +176,23 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@graphiql/plugin-code-exporter@0.3.0": + version "0.3.0" + resolved "https://registry.npmjs.org/@graphiql/plugin-code-exporter/-/plugin-code-exporter-0.3.0.tgz" + integrity sha512-IQvrNJEPRPdMIG9E3nVbznGgtYYsZv5kU8LRayBqHqXoIaOsL4CTXx2PjmjJptbFuDMQxzlkYCOyW+QUcLQ2XA== + dependencies: + graphiql-code-exporter "^3.0.3" + +"@graphiql/plugin-explorer@0.3.0": + version "0.3.0" + resolved "https://registry.npmjs.org/@graphiql/plugin-explorer/-/plugin-explorer-0.3.0.tgz" + integrity sha512-ZXAfzFXuqBPzD3HNSelh23eCRE1Qdsi2ndZyOqAjcy6bxo3lSj8wrM8cN1+T1Wo+0U39SiTkdmlElZTaip7RXA== + dependencies: + graphiql-explorer "^0.9.0" + "@graphiql/react@^0.17.6": version "0.17.6" - resolved "https://registry.yarnpkg.com/@graphiql/react/-/react-0.17.6.tgz#54e3745f74ccf5cd69540aecc9dbcd15a7e28c1c" + resolved "https://registry.npmjs.org/@graphiql/react/-/react-0.17.6.tgz" integrity sha512-3k1paSRbRwVNxr2U80xnRhkws8tSErWlETJvEQBmqRcWbt0+WmwFJorkLnG1n3Wj0Ho6k4a2BAiTfJ6F4SPrLg== dependencies: "@graphiql/toolkit" "^0.8.4" @@ -198,7 +212,7 @@ "@graphiql/toolkit@^0.8.4": version "0.8.4" - resolved "https://registry.yarnpkg.com/@graphiql/toolkit/-/toolkit-0.8.4.tgz#8b697d140a3e96a6702428cbb8da4e8eb29162b3" + resolved "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.8.4.tgz" integrity sha512-cFUGqh3Dau+SD3Vq9EFlZrhzYfaHKyOJveFtaCR+U5Cn/S68p7oy+vQBIdwtO6J2J58FncnwBbVRfr+IvVfZqQ== dependencies: "@n1ru4l/push-pull-async-iterable-iterator" "^3.1.0" @@ -272,12 +286,12 @@ "@n1ru4l/push-pull-async-iterable-iterator@^3.1.0": version "3.2.0" - resolved "https://registry.yarnpkg.com/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz#c15791112db68dd9315d329d652b7e797f737655" + resolved "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.2.0.tgz" integrity sha512-3fkKj25kEjsfObL6IlKPAlHYPq/oYwUkkQ03zsTTiDjD7vg/RxjdiLeCydqtxHZP0JgsXL3D/X5oAkMGzuUp/Q== "@near-lake/primitives@0.1.0": version "0.1.0" - resolved "https://registry.yarnpkg.com/@near-lake/primitives/-/primitives-0.1.0.tgz#c9dc196cad82b668e773eab7f673edfc6a877cea" + resolved "https://registry.npmjs.org/@near-lake/primitives/-/primitives-0.1.0.tgz" integrity sha512-SvL6mA0SsqAz5AC2811I+cI9Mpayax8VsoRbY0Bizk5eYiGCT1u1iBBa8f1nikquDfJCEK+sBCt751Nz/xoZjw== "@next/env@13.1.6": @@ -309,12 +323,12 @@ "@next/swc-darwin-arm64@13.1.6": version "13.1.6" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.6.tgz#ec1b90fd9bf809d8b81004c5182e254dced4ad96" + resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.1.6.tgz" integrity sha512-KKRQH4DDE4kONXCvFMNBZGDb499Hs+xcFAwvj+rfSUssIDrZOlyfJNy55rH5t2Qxed1e4K80KEJgsxKQN1/fyw== "@next/swc-darwin-x64@13.1.6": version "13.1.6" - resolved "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.6.tgz" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.1.6.tgz#e869ac75d16995eee733a7d1550322d9051c1eb4" integrity sha512-/uOky5PaZDoaU99ohjtNcDTJ6ks/gZ5ykTQDvNZDjIoCxFe3+t06bxsTPY6tAO6uEAw5f6vVFX5H5KLwhrkZCA== "@next/swc-freebsd-x64@13.1.6": @@ -402,7 +416,7 @@ "@reach/auto-id@0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.17.0.tgz#60cce65eb7a0d6de605820727f00dfe2b03b5f17" + resolved "https://registry.npmjs.org/@reach/auto-id/-/auto-id-0.17.0.tgz" integrity sha512-ud8iPwF52RVzEmkHq1twuqGuPA+moreumUHdtgvU3sr3/15BNhwp3KyDLrKKSz0LP1r3V4pSdyF9MbYM8BoSjA== dependencies: "@reach/utils" "0.17.0" @@ -410,7 +424,7 @@ "@reach/combobox@^0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/combobox/-/combobox-0.17.0.tgz#fb9d71d2d5aff3b339dce0ec5e3b73628a51b009" + resolved "https://registry.npmjs.org/@reach/combobox/-/combobox-0.17.0.tgz" integrity sha512-2mYvU5agOBCQBMdlM4cri+P1BbNwp05P1OuDyc33xJSNiBG7BMy4+ZSHJ0X4fyle6rHwSgCAOCLOeWV1XUYjoQ== dependencies: "@reach/auto-id" "0.17.0" @@ -424,7 +438,7 @@ "@reach/descendants@0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/descendants/-/descendants-0.17.0.tgz#3fb087125a67870acd4dee1528449ed546829b67" + resolved "https://registry.npmjs.org/@reach/descendants/-/descendants-0.17.0.tgz" integrity sha512-c7lUaBfjgcmKFZiAWqhG+VnXDMEhPkI4kAav/82XKZD6NVvFjsQOTH+v3tUkskrAPV44Yuch0mFW/u5Ntifr7Q== dependencies: "@reach/utils" "0.17.0" @@ -432,7 +446,7 @@ "@reach/dialog@^0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/dialog/-/dialog-0.17.0.tgz#81c48dd4405945dfc6b6c3e5e125db2c4324e9e8" + resolved "https://registry.npmjs.org/@reach/dialog/-/dialog-0.17.0.tgz" integrity sha512-AnfKXugqDTGbeG3c8xDcrQDE4h9b/vnc27Sa118oQSquz52fneUeX9MeFb5ZEiBJK8T5NJpv7QUTBIKnFCAH5A== dependencies: "@reach/portal" "0.17.0" @@ -444,7 +458,7 @@ "@reach/dropdown@0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/dropdown/-/dropdown-0.17.0.tgz#8140bb2e6a045f91e07c6d5a6ff960958df2ef33" + resolved "https://registry.npmjs.org/@reach/dropdown/-/dropdown-0.17.0.tgz" integrity sha512-qBTIGInhxtPHtdj4Pl2XZgZMz3e37liydh0xR3qc48syu7g71sL4nqyKjOzThykyfhA3Pb3/wFgsFJKGTSdaig== dependencies: "@reach/auto-id" "0.17.0" @@ -455,7 +469,7 @@ "@reach/listbox@^0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/listbox/-/listbox-0.17.0.tgz#e709f31056bb77781e74c9f0b69bf9ec8efbbc8b" + resolved "https://registry.npmjs.org/@reach/listbox/-/listbox-0.17.0.tgz" integrity sha512-AMnH1P6/3VKy2V/nPb4Es441arYR+t4YRdh9jdcFVrCOD6y7CQrlmxsYjeg9Ocdz08XpdoEBHM3PKLJqNAUr7A== dependencies: "@reach/auto-id" "0.17.0" @@ -467,7 +481,7 @@ "@reach/machine@0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/machine/-/machine-0.17.0.tgz#4e4bbf66e3c3934e65243485ac84f6f8fa3d9a24" + resolved "https://registry.npmjs.org/@reach/machine/-/machine-0.17.0.tgz" integrity sha512-9EHnuPgXzkbRENvRUzJvVvYt+C2jp7PGN0xon7ffmKoK8rTO6eA/bb7P0xgloyDDQtu88TBUXKzW0uASqhTXGA== dependencies: "@reach/utils" "0.17.0" @@ -476,7 +490,7 @@ "@reach/menu-button@^0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.17.0.tgz#9f40979129b61f8bdc19590c527f7ed4883d2dce" + resolved "https://registry.npmjs.org/@reach/menu-button/-/menu-button-0.17.0.tgz" integrity sha512-YyuYVyMZKamPtivoEI6D0UEILYH3qZtg4kJzEAuzPmoR/aHN66NZO75Fx0gtjG1S6fZfbiARaCOZJC0VEiDOtQ== dependencies: "@reach/dropdown" "0.17.0" @@ -488,12 +502,12 @@ "@reach/observe-rect@1.2.0": version "1.2.0" - resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2" + resolved "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.2.0.tgz" integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ== "@reach/popover@0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/popover/-/popover-0.17.0.tgz#feda6961f37d17b8738d2d52af6bfc5c4584464f" + resolved "https://registry.npmjs.org/@reach/popover/-/popover-0.17.0.tgz" integrity sha512-yYbBF4fMz4Ml4LB3agobZjcZ/oPtPsNv70ZAd7lEC2h7cvhF453pA+zOBGYTPGupKaeBvgAnrMjj7RnxDU5hoQ== dependencies: "@reach/portal" "0.17.0" @@ -504,7 +518,7 @@ "@reach/portal@0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.17.0.tgz#1dd69ffc8ffc8ba3e26dd127bf1cc4b15f0c6bdc" + resolved "https://registry.npmjs.org/@reach/portal/-/portal-0.17.0.tgz" integrity sha512-+IxsgVycOj+WOeNPL2NdgooUdHPSY285wCtj/iWID6akyr4FgGUK7sMhRM9aGFyrGpx2vzr+eggbUmAVZwOz+A== dependencies: "@reach/utils" "0.17.0" @@ -513,7 +527,7 @@ "@reach/rect@0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.17.0.tgz#804f0cfb211e0beb81632c64d4532ec9d1d73c48" + resolved "https://registry.npmjs.org/@reach/rect/-/rect-0.17.0.tgz" integrity sha512-3YB7KA5cLjbLc20bmPkJ06DIfXSK06Cb5BbD2dHgKXjUkT9WjZaLYIbYCO8dVjwcyO3GCNfOmPxy62VsPmZwYA== dependencies: "@reach/observe-rect" "1.2.0" @@ -524,7 +538,7 @@ "@reach/tooltip@^0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.17.0.tgz#044b43de248a05b18641b4220310983cb54675a2" + resolved "https://registry.npmjs.org/@reach/tooltip/-/tooltip-0.17.0.tgz" integrity sha512-HP8Blordzqb/Cxg+jnhGmWQfKgypamcYLBPlcx6jconyV5iLJ5m93qipr1giK7MqKT2wlsKWy44ZcOrJ+Wrf8w== dependencies: "@reach/auto-id" "0.17.0" @@ -538,7 +552,7 @@ "@reach/utils@0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.17.0.tgz#3d1d2ec56d857f04fe092710d8faee2b2b121303" + resolved "https://registry.npmjs.org/@reach/utils/-/utils-0.17.0.tgz" integrity sha512-M5y8fCBbrWeIsxedgcSw6oDlAMQDkl5uv3VnMVJ7guwpf4E48Xlh1v66z/1BgN/WYe2y8mB/ilFD2nysEfdGeA== dependencies: tiny-warning "^1.0.3" @@ -546,7 +560,7 @@ "@reach/visually-hidden@0.17.0", "@reach/visually-hidden@^0.17.0": version "0.17.0" - resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.17.0.tgz#033adba10b5ec419649da8d6bd8e46db06d4c3a1" + resolved "https://registry.npmjs.org/@reach/visually-hidden/-/visually-hidden-0.17.0.tgz" integrity sha512-T6xF3Nv8vVnjVkGU6cm0+kWtvliLqPAo8PcZ+WxkKacZsaHTjaZb4v1PaCcyQHmuTNT/vtTVNOJLG0SjQOIb7g== dependencies: prop-types "^15.7.2" @@ -594,9 +608,9 @@ tslib "^2.4.0" "@types/json-schema@^7.0.8": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + version "7.0.12" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== "@types/json5@^0.0.29": version "0.0.29" @@ -692,7 +706,7 @@ "@xstate/fsm@1.4.0": version "1.4.0" - resolved "https://registry.yarnpkg.com/@xstate/fsm/-/fsm-1.4.0.tgz#6fd082336fde4d026e9e448576189ee5265fa51a" + resolved "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.4.0.tgz" integrity sha512-uTHDeu2xI5E1IFwf37JFQM31RrH7mY7877RqPBS4ZqSNUwoLDuct8AhBWaXGnVizBAYyimVwgCyGa9z/NiRhXA== acorn-jsx@^5.3.2: @@ -707,7 +721,7 @@ acorn@^8.8.0: ajv-keywords@^3.5.2: version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: @@ -850,17 +864,17 @@ base-x@^3.0.2: base64-js@^1.3.1: version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== big-integer@^1.6.48: version "1.6.51" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz" integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== big.js@^5.2.2: version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== bn.js@5.2.1, bn.js@^5.2.0: @@ -906,7 +920,7 @@ bs58@^4.0.0: buffer@^6.0.3: version "6.0.3" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + resolved "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== dependencies: base64-js "^1.3.1" @@ -931,9 +945,9 @@ camelize@^1.0.0: integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== caniuse-lite@^1.0.30001406: - version "1.0.30001451" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz" - integrity sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w== + version "1.0.30001527" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001527.tgz" + integrity sha512-YkJi7RwPgWtXVSgK4lG9AHH57nSzvvOp9MesgXmw4Q7n0C3H04L0foHqfxcmSAm5AcWb8dW9AYj2tR7/5GnddQ== capability@^0.2.5: version "0.2.5" @@ -969,19 +983,19 @@ client-only@0.0.1: clsx@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== codemirror-graphql@^2.0.8: version "2.0.8" - resolved "https://registry.yarnpkg.com/codemirror-graphql/-/codemirror-graphql-2.0.8.tgz#0d63cc6a5c5792081041e319078cfc2969dd97ef" + resolved "https://registry.npmjs.org/codemirror-graphql/-/codemirror-graphql-2.0.8.tgz" integrity sha512-EU+pXsSKZJAFVdF8j5hbB5gqXsDDjsBiJoohQq09yhsr69pzaI8ZrXjmpuR4CMyf9jgqcz5KK7rsTmxDHmeJPQ== dependencies: graphql-language-service "5.1.6" codemirror@^5.65.3: version "5.65.13" - resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.13.tgz#c098a6f409db8b5a7c5722788bd9fa3bb2367f2e" + resolved "https://registry.npmjs.org/codemirror/-/codemirror-5.65.13.tgz" integrity sha512-SVWEzKXmbHmTQQWaz03Shrh4nybG0wXx2MEu3FO4ezbPW8IbnZEd5iGHGEffSUaitKYa3i+pHpBsSvw8sPHtzg== color-convert@^1.9.0: @@ -1010,7 +1024,7 @@ color-name@~1.1.4: commander@^2.19.0: version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== concat-map@0.0.1: @@ -1018,9 +1032,9 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -copy-to-clipboard@^3.2.0: +copy-to-clipboard@^3.0.8, copy-to-clipboard@^3.2.0: version "3.3.3" - resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" + resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz" integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== dependencies: toggle-selection "^1.0.6" @@ -1130,7 +1144,7 @@ dequal@^2.0.2, dequal@^2.0.3: detect-node-es@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== dir-glob@^3.0.1: @@ -1142,7 +1156,7 @@ dir-glob@^3.0.1: discontinuous-range@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" + resolved "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz" integrity sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ== doctrine@^2.1.0: @@ -1174,20 +1188,20 @@ emoji-regex@^9.2.2: emojis-list@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== enhanced-resolve@^5.10.0: - version "5.12.0" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz" - integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ== + version "5.15.0" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" entities@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" + resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== error-polyfill@^0.1.3: @@ -1574,7 +1588,7 @@ flatted@^3.1.0: focus-lock@^0.11.6: version "0.11.6" - resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.11.6.tgz#e8821e21d218f03e100f7dc27b733f9c4f61e683" + resolved "https://registry.npmjs.org/focus-lock/-/focus-lock-0.11.6.tgz" integrity sha512-KSuV3ur4gf2KqMNoZx3nXNVhqCkn42GuTYCX4tXPEwf0MjpFQmNMiN6m7dXaUXgIoivL6/65agoUMg4RLS0Vbg== dependencies: tslib "^2.0.3" @@ -1622,7 +1636,7 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ get-nonce@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + resolved "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz" integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== get-symbol-description@^1.0.0: @@ -1733,9 +1747,21 @@ grapheme-splitter@^1.0.4: resolved "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphiql-code-exporter@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/graphiql-code-exporter/-/graphiql-code-exporter-3.0.3.tgz" + integrity sha512-Ml3J/ojCQ56qrIgJPDCrWQ2cpI/6yio2P1tHPBuvhGJ2zVSUCH/D+v1DIwXIzsAMwqq0WkaknqH3iuA6LD5A5A== + dependencies: + copy-to-clipboard "^3.0.8" + +graphiql-explorer@^0.9.0: + version "0.9.0" + resolved "https://registry.npmjs.org/graphiql-explorer/-/graphiql-explorer-0.9.0.tgz" + integrity sha512-fZC/wsuatqiQDO2otchxriFO0LaWIo/ovF/CQJ1yOudmY0P7pzDiP+l9CEHUiWbizk3e99x6DQG4XG1VxA+d6A== + graphiql@^2.4.1: version "2.4.7" - resolved "https://registry.yarnpkg.com/graphiql/-/graphiql-2.4.7.tgz#77eae9e8b31628bad363384c5b382de9fad1ff86" + resolved "https://registry.npmjs.org/graphiql/-/graphiql-2.4.7.tgz" integrity sha512-Fm3fVI65EPyXy+PdbeQUyODTwl2NhpZ47msGnGwpDvdEzYdgF7pPrxL96xCfF31KIauS4+ceEJ+ZwEe5iLWiQw== dependencies: "@graphiql/react" "^0.17.6" @@ -1745,7 +1771,7 @@ graphiql@^2.4.1: graphql-language-service@5.1.6, graphql-language-service@^5.1.6: version "5.1.6" - resolved "https://registry.yarnpkg.com/graphql-language-service/-/graphql-language-service-5.1.6.tgz#0d6d2b2bb09cf0d02c82fd97628dfdc63ce7936b" + resolved "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.1.6.tgz" integrity sha512-sl9HTlE/sBoFvZ2SPGnApwpp/a4ahl1d49SOxGm2OIYOslFv00MK7AYms9Yx91omOwAp74is10S7Cjamh5TRQw== dependencies: nullthrows "^1.0.0" @@ -1822,7 +1848,7 @@ http-errors@^1.7.2: ieee754@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== ignore@^5.2.0: @@ -1969,14 +1995,14 @@ is-path-inside@^3.0.3: is-plain-object@^2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-primitive@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + resolved "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz" integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== is-regex@^1.1.4: @@ -2063,7 +2089,7 @@ isexe@^2.0.0: isobject@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== js-sdsl@^4.1.4: @@ -2112,7 +2138,7 @@ json5@^1.0.1: json5@^2.1.2: version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.3: @@ -2145,14 +2171,14 @@ levn@^0.4.1: linkify-it@^3.0.1: version "3.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" + resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz" integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== dependencies: uc.micro "^1.0.1" loader-utils@^2.0.0: version "2.0.4" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + resolved "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz" integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== dependencies: big.js "^5.2.2" @@ -2192,7 +2218,7 @@ lru-cache@^6.0.0: markdown-it@^12.2.0: version "12.3.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz" integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== dependencies: argparse "^2.0.1" @@ -2203,7 +2229,7 @@ markdown-it@^12.2.0: mdurl@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== merge2@^1.3.0, merge2@^1.4.1: @@ -2213,7 +2239,7 @@ merge2@^1.3.0, merge2@^1.4.1: meros@^1.1.4: version "1.3.0" - resolved "https://registry.yarnpkg.com/meros/-/meros-1.3.0.tgz#c617d2092739d55286bf618129280f362e6242f2" + resolved "https://registry.npmjs.org/meros/-/meros-1.3.0.tgz" integrity sha512-2BNGOimxEz5hmjUG2FwoxCt5HN7BXdaWyFqEwxPTrJzVdABtrL4TiHTcsWSFAxPQ/tOnEaQEJh3qWq71QRMY+w== micromatch@^4.0.4: @@ -2238,7 +2264,7 @@ minimist@^1.2.0, minimist@^1.2.6: moo@^0.5.0: version "0.5.2" - resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c" + resolved "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz" integrity sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q== ms@2.1.2, ms@^2.1.1: @@ -2280,12 +2306,12 @@ near-api-js@1.1.0: near-social-bridge@^1.4.1: version "1.4.1" - resolved "https://registry.yarnpkg.com/near-social-bridge/-/near-social-bridge-1.4.1.tgz#fa6757a95a0de5007bb0ebf28d46810c1a255bac" + resolved "https://registry.npmjs.org/near-social-bridge/-/near-social-bridge-1.4.1.tgz" integrity sha512-e8hTbBI9Pwq1aKpM6kvZ3Sy1DjjcAOau0wF/Sp9LsT7q2+F1QxeDMkO7ndQrkJI7K0+CIAvjFmoex0ikxBtnug== nearley@^2.20.1: version "2.20.1" - resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474" + resolved "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz" integrity sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ== dependencies: commander "^2.19.0" @@ -2325,16 +2351,23 @@ node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" +node-sql-parser@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/node-sql-parser/-/node-sql-parser-4.10.0.tgz#a53d7a7570ad62ffccfead4391b40ca09dc996e8" + integrity sha512-P4LZNX8drf+0X5zPtcE5o1SV7Wn4VpTGSYOnN8uY+TswtHrg3ymb193tYpF8EMp2LhGqqDUqTAnCr8hqjN3uQw== + dependencies: + big-integer "^1.6.48" + node-sql-parser@^4.4.0: version "4.6.6" - resolved "https://registry.yarnpkg.com/node-sql-parser/-/node-sql-parser-4.6.6.tgz#910fcd4ba0132d9a5a8c312637313acc2b43b24a" + resolved "https://registry.npmjs.org/node-sql-parser/-/node-sql-parser-4.6.6.tgz" integrity sha512-zpash5xnRY6+0C9HFru32iRJV1LTkwtrVpO90i385tYVF6efyXK/B3Nsq/15Fuv2utxrqHNjKtL55OHb8sl+eQ== dependencies: big-integer "^1.6.48" nullthrows@^1.0.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz" integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== o3@^1.0.3: @@ -2517,7 +2550,7 @@ prelude-ls@^1.2.1: prettier-plugin-sql@^0.13.0: version "0.13.0" - resolved "https://registry.yarnpkg.com/prettier-plugin-sql/-/prettier-plugin-sql-0.13.0.tgz#047b395ca1c04b332772550ca69aa83121802c37" + resolved "https://registry.npmjs.org/prettier-plugin-sql/-/prettier-plugin-sql-0.13.0.tgz" integrity sha512-Ui9603tDD6PFyr7JvIEoE6cIFMQnJVDriG+oLyVThsGo/MIl5ek18JhH3xtox9ux8jvyww/FUFrJzxpZ7FIdvw== dependencies: node-sql-parser "^4.4.0" @@ -2558,12 +2591,12 @@ queue-microtask@^1.2.2: railroad-diagrams@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" + resolved "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz" integrity sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A== randexp@0.4.6: version "0.4.6" - resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + resolved "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz" integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== dependencies: discontinuous-range "1.0.0" @@ -2571,7 +2604,7 @@ randexp@0.4.6: raw-loader@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" + resolved "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz" integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA== dependencies: loader-utils "^2.0.0" @@ -2579,7 +2612,7 @@ raw-loader@^4.0.2: react-bootstrap-icons@^1.10.3: version "1.10.3" - resolved "https://registry.yarnpkg.com/react-bootstrap-icons/-/react-bootstrap-icons-1.10.3.tgz#5d64a93c7b172856b03c7d3cd5119a025b094966" + resolved "https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.10.3.tgz" integrity sha512-j4hSby6gT9/enhl3ybB1tfr1slZNAYXDVntcRrmVjxB3//2WwqrzpESVqKhyayYVaWpEtnwf9wgUQ03cuziwrw== dependencies: prop-types "^15.7.2" @@ -2604,7 +2637,7 @@ react-bootstrap@^2.7.2: react-clientside-effect@^1.2.6: version "1.2.6" - resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz#29f9b14e944a376b03fb650eed2a754dd128ea3a" + resolved "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.6.tgz" integrity sha512-XGGGRQAKY+q25Lz9a/4EPqom7WRjz3z9R2k4jhVKA/puQFH/5Nt27vFZYql4m4NVNdUvX8PS3O7r/Zzm7cjUlg== dependencies: "@babel/runtime" "^7.12.13" @@ -2619,7 +2652,7 @@ react-dom@18.2.0: react-focus-lock@^2.5.2: version "2.9.4" - resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.9.4.tgz#4753f6dcd167c39050c9d84f9c63c71b3ff8462e" + resolved "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.9.4.tgz" integrity sha512-7pEdXyMseqm3kVjhdVH18sovparAzLg5h6WvIx7/Ck3ekjhrrDMEegHSa3swwC8wgfdd7DIdUVRGeiHT9/7Sgg== dependencies: "@babel/runtime" "^7.0.0" @@ -2641,7 +2674,7 @@ react-lifecycles-compat@^3.0.4: react-remove-scroll-bar@^2.3.4: version "2.3.4" - resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz#53e272d7a5cb8242990c7f144c44d8bd8ab5afd9" + resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz" integrity sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A== dependencies: react-style-singleton "^2.2.1" @@ -2649,7 +2682,7 @@ react-remove-scroll-bar@^2.3.4: react-remove-scroll@^2.4.3: version "2.5.6" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.6.tgz#7510b8079e9c7eebe00e65a33daaa3aa29a10336" + resolved "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.6.tgz" integrity sha512-bO856ad1uDYLefgArk559IzUNeQ6SWH4QnrevIUjH+GczV56giDfl3h0Idptf2oIKxQmd1p9BN25jleKodTALg== dependencies: react-remove-scroll-bar "^2.3.4" @@ -2660,7 +2693,7 @@ react-remove-scroll@^2.4.3: react-style-singleton@^2.2.1: version "2.2.1" - resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" + resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz" integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== dependencies: get-nonce "^1.0.0" @@ -2669,7 +2702,7 @@ react-style-singleton@^2.2.1: react-switch@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/react-switch/-/react-switch-7.0.0.tgz#400990bb9822864938e343ed24f13276a617bdc0" + resolved "https://registry.npmjs.org/react-switch/-/react-switch-7.0.0.tgz" integrity sha512-KkDeW+cozZXI6knDPyUt3KBN1rmhoVYgAdCJqAh7st7tk8YE6N0iR89zjCWO8T8dUTeJGTR0KU+5CHCRMRffiA== dependencies: prop-types "^15.7.2" @@ -2735,7 +2768,7 @@ resolve@^2.0.0-next.4: ret@~0.1.10: version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + resolved "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== reusify@^1.0.4: @@ -2779,9 +2812,9 @@ scheduler@^0.23.0: loose-envify "^1.1.0" schema-utils@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + 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== dependencies: "@types/json-schema" "^7.0.8" ajv "^6.12.5" @@ -2801,7 +2834,7 @@ semver@^7.3.7: set-value@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" + resolved "https://registry.npmjs.org/set-value/-/set-value-4.1.0.tgz" integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== dependencies: is-plain-object "^2.0.4" @@ -2855,7 +2888,7 @@ source-map-js@^1.0.2: sql-formatter@^11.0.2: version "11.0.2" - resolved "https://registry.yarnpkg.com/sql-formatter/-/sql-formatter-11.0.2.tgz#2fde373c8c1845f8ee9f201d2eccb1fb365cd893" + resolved "https://registry.npmjs.org/sql-formatter/-/sql-formatter-11.0.2.tgz" integrity sha512-6QumAdGHEnI5dXEq1d0aBRP876AyA9Wp/UE7wopKNA2Mp9sKGRKVqGgoWHk4dr0J0nceesC85Y0p36qmGoNqhw== dependencies: argparse "^2.0.1" @@ -2979,7 +3012,7 @@ synckit@^0.8.4: tabbable@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261" + resolved "https://registry.npmjs.org/tabbable/-/tabbable-4.0.0.tgz" integrity sha512-H1XoH1URcBOa/rZZWxLxHCtOdVUEev+9vo5YdYhC9tCY4wnybX+VQrCYuy9ubkg69fCBxCONJOSLGfw0DWMffQ== tapable@^2.2.0: @@ -3007,7 +3040,7 @@ tiny-glob@^0.2.9: tiny-warning@^1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" + resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== to-fast-properties@^2.0.0: @@ -3024,7 +3057,7 @@ to-regex-range@^5.0.1: toggle-selection@^1.0.6: version "1.0.6" - resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz" integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== toidentifier@1.0.1: @@ -3059,7 +3092,7 @@ tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.5.0: tslib@^2.0.3, tslib@^2.3.0: version "2.5.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.2.tgz#1b6f07185c881557b0ffa84b111a0106989e8338" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz" integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA== tsutils@^3.21.0: @@ -3107,7 +3140,7 @@ u3@^0.1.1: uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" + resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== unbox-primitive@^1.0.2: @@ -3139,14 +3172,14 @@ uri-js@^4.2.2: use-callback-ref@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.0.tgz#772199899b9c9a50526fedc4993fc7fa1f7e32d5" + resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz" integrity sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w== dependencies: tslib "^2.0.0" use-sidecar@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" + resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz" integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== dependencies: detect-node-es "^1.1.0" @@ -3154,7 +3187,7 @@ use-sidecar@^1.1.2: vscode-languageserver-types@^3.17.1: version "3.17.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" + resolved "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz" integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== warning@^4.0.0, warning@^4.0.3: diff --git a/hasura/config.yaml b/hasura/config.yaml new file mode 100644 index 000000000..d13faa054 --- /dev/null +++ b/hasura/config.yaml @@ -0,0 +1,7 @@ +version: 3 +endpoint: http://localhost:8080 +admin_secret: myadminsecretkey +metadata_directory: metadata +actions: + kind: synchronous + handler_webhook_baseurl: http://localhost:3000 diff --git a/hasura/metadata/actions.graphql b/hasura/metadata/actions.graphql new file mode 100644 index 000000000..e69de29bb diff --git a/hasura/metadata/actions.yaml b/hasura/metadata/actions.yaml new file mode 100644 index 000000000..1edb4c2ff --- /dev/null +++ b/hasura/metadata/actions.yaml @@ -0,0 +1,6 @@ +actions: [] +custom_types: + enums: [] + input_objects: [] + objects: [] + scalars: [] diff --git a/hasura/metadata/allow_list.yaml b/hasura/metadata/allow_list.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/hasura/metadata/allow_list.yaml @@ -0,0 +1 @@ +[] diff --git a/hasura/metadata/cron_triggers.yaml b/hasura/metadata/cron_triggers.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/hasura/metadata/cron_triggers.yaml @@ -0,0 +1 @@ +[] diff --git a/hasura/metadata/databases/databases.yaml b/hasura/metadata/databases/databases.yaml new file mode 100644 index 000000000..65a11b202 --- /dev/null +++ b/hasura/metadata/databases/databases.yaml @@ -0,0 +1,14 @@ +- name: default + kind: postgres + configuration: + connection_info: + database_url: + from_env: HASURA_GRAPHQL_DATABASE_URL + isolation_level: read-committed + pool_settings: + connection_lifetime: 600 + idle_timeout: 180 + max_connections: 50 + retries: 1 + use_prepared_statements: true + tables: "!include default/tables/tables.yaml" diff --git a/hasura/metadata/databases/default/tables/public_indexer_log_entries.yaml b/hasura/metadata/databases/default/tables/public_indexer_log_entries.yaml new file mode 100644 index 000000000..c95174150 --- /dev/null +++ b/hasura/metadata/databases/default/tables/public_indexer_log_entries.yaml @@ -0,0 +1,23 @@ +table: + name: indexer_log_entries + schema: public +insert_permissions: +- permission: + check: {} + columns: + - block_height + - function_name + - message + - timestamp + - id + role: append +select_permissions: +- permission: + columns: + - block_height + - function_name + - message + - timestamp + - id + filter: {} + role: append diff --git a/hasura/metadata/databases/default/tables/public_indexer_state.yaml b/hasura/metadata/databases/default/tables/public_indexer_state.yaml new file mode 100644 index 000000000..51084d5a3 --- /dev/null +++ b/hasura/metadata/databases/default/tables/public_indexer_state.yaml @@ -0,0 +1,31 @@ +table: + name: indexer_state + schema: public +insert_permissions: +- permission: + check: {} + columns: + - function_name + - current_block_height + - current_historical_block_height + - status + role: append +select_permissions: +- permission: + columns: + - function_name + - current_block_height + - current_historical_block_height + - status + filter: {} + role: append +update_permissions: +- permission: + check: {} + columns: + - function_name + - current_block_height + - current_historical_block_height + - status + filter: {} + role: append diff --git a/hasura/metadata/databases/default/tables/tables.yaml b/hasura/metadata/databases/default/tables/tables.yaml new file mode 100644 index 000000000..fb74b4277 --- /dev/null +++ b/hasura/metadata/databases/default/tables/tables.yaml @@ -0,0 +1,2 @@ +- "!include public_indexer_log_entries.yaml" +- "!include public_indexer_state.yaml" diff --git a/hasura/metadata/query_collections.yaml b/hasura/metadata/query_collections.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/hasura/metadata/query_collections.yaml @@ -0,0 +1 @@ +[] diff --git a/hasura/metadata/remote_schemas.yaml b/hasura/metadata/remote_schemas.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/hasura/metadata/remote_schemas.yaml @@ -0,0 +1 @@ +[] diff --git a/hasura/metadata/rest_endpoints.yaml b/hasura/metadata/rest_endpoints.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/hasura/metadata/rest_endpoints.yaml @@ -0,0 +1 @@ +[] diff --git a/hasura/metadata/version.yaml b/hasura/metadata/version.yaml new file mode 100644 index 000000000..0a70affa4 --- /dev/null +++ b/hasura/metadata/version.yaml @@ -0,0 +1 @@ +version: 3 diff --git a/hasura/migrations/default/1691364619300_init/down.sql b/hasura/migrations/default/1691364619300_init/down.sql new file mode 100644 index 000000000..9ba2f7739 --- /dev/null +++ b/hasura/migrations/default/1691364619300_init/down.sql @@ -0,0 +1,2 @@ +DROP TABLE public.indexer_log_entries; +DROP TABLE public.indexer_state; diff --git a/hasura/migrations/default/1691364619300_init/up.sql b/hasura/migrations/default/1691364619300_init/up.sql new file mode 100644 index 000000000..4c1dad71f --- /dev/null +++ b/hasura/migrations/default/1691364619300_init/up.sql @@ -0,0 +1,25 @@ +SET check_function_bodies = false; + +CREATE TABLE public.indexer_log_entries ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + function_name text NOT NULL, + block_height numeric NOT NULL, + "timestamp" timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + message text +); + +CREATE TABLE public.indexer_state ( + function_name character varying NOT NULL, + current_block_height numeric(21,0) NOT NULL, + status text, + current_historical_block_height numeric(21,0) +); + +ALTER TABLE ONLY public.indexer_log_entries + ADD CONSTRAINT indexer_log_entries_pkey PRIMARY KEY (id); + +ALTER TABLE ONLY public.indexer_state + ADD CONSTRAINT indexer_state_pkey PRIMARY KEY (function_name); + +CREATE INDEX idx_function_name ON indexer_log_entries(function_name); +CREATE INDEX idx_timestamp ON indexer_log_entries("timestamp"); diff --git a/indexer/Dockerfile b/indexer/Dockerfile index c8693071f..925ed46f5 100644 --- a/indexer/Dockerfile +++ b/indexer/Dockerfile @@ -1,30 +1,16 @@ FROM rust:1.68 AS build - +ARG CARGO_BUILD_MODE=release WORKDIR /tmp/ -COPY Cargo.toml Cargo.lock ./ -COPY storage/Cargo.toml ./storage/ -COPY indexer_rule_type/Cargo.toml ./indexer_rule_type/ -COPY indexer_rules_engine/Cargo.toml ./indexer_rules_engine/ -COPY queryapi_coordinator/Cargo.toml ./queryapi_coordinator/ - -# We have to use sparse-registry cargo feature to avoid running out of RAM (version 1.68+) -ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse -RUN /bin/bash -c "mkdir -p {queryapi_coordinator,indexer_rule_type,indexer_rules_engine,storage}/src" && \ - echo 'fn main() {}' > queryapi_coordinator/src/main.rs && \ - touch indexer_rule_type/src/lib.rs && \ - touch indexer_rules_engine/src/lib.rs && \ - touch storage/src/lib.rs && \ - cargo build - COPY ./ ./ - -RUN cargo build --release --package queryapi_coordinator --offline - +RUN if [ "$CARGO_BUILD_MODE" = "debug" ]; then \ + cargo build --package queryapi_coordinator; \ + else \ + cargo build --release --package queryapi_coordinator; \ + fi FROM ubuntu:20.04 - +ARG CARGO_BUILD_MODE=release RUN apt update && apt install -yy openssl ca-certificates - USER nobody -COPY --from=build /tmp/target/release/queryapi_coordinator /queryapi_coordinator +COPY --from=build /tmp/target/$CARGO_BUILD_MODE/queryapi_coordinator /queryapi_coordinator ENTRYPOINT ["/queryapi_coordinator"] diff --git a/indexer/docker-compose.yml b/indexer/docker-compose.yml deleted file mode 100644 index 9be7e5ced..000000000 --- a/indexer/docker-compose.yml +++ /dev/null @@ -1,26 +0,0 @@ -version: "3.9" # optional since v1.27.0 -services: - - actions_queryapi_coordinator: - build: - context: . - args: - - chain_id=mainnet - env_file: ./queryapi_coordinator/.env - links: - - redis - environment: - - REDIS_CONNECTION_STRING=redis://redis - command: - - mainnet - - from-interruption - redis: - image: redis - command: - - redis-server - - "--save 60 1" - - "--loglevel warning" - volumes: - - ./redis/data:/data - ports: - - "6379:6379" \ No newline at end of file diff --git a/indexer/queryapi_coordinator/src/cache.rs b/indexer/queryapi_coordinator/src/cache.rs deleted file mode 100644 index fb7501801..000000000 --- a/indexer/queryapi_coordinator/src/cache.rs +++ /dev/null @@ -1,278 +0,0 @@ -use std::str::FromStr; - -use borsh::{BorshDeserialize, BorshSerialize}; -use cached::Cached; -use futures::future::try_join_all; -use serde::{Deserialize, Serialize}; - -use near_jsonrpc_client::errors::JsonRpcError; -use near_jsonrpc_primitives::types::query::RpcQueryError; -use near_lake_framework::near_indexer_primitives::{ - types, - views::{self, ExecutionStatusView}, - CryptoHash, IndexerExecutionOutcomeWithReceipt, IndexerTransactionWithOutcome, -}; - -#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug)] -pub(crate) struct CacheValue { - pub transaction_hash: String, - pub parent_receipt_id: Option, - pub children_receipt_ids: Vec, -} - -pub(crate) async fn update_all( - streamer_message: &near_lake_framework::near_indexer_primitives::StreamerMessage, - redis_connection_manager: &storage::ConnectionManager, -) -> anyhow::Result<()> { - cache_txs_and_receipts(streamer_message, redis_connection_manager).await?; - Ok(()) -} - -pub(crate) async fn cache_txs_and_receipts( - streamer_message: &near_lake_framework::near_indexer_primitives::StreamerMessage, - redis_connection_manager: &storage::ConnectionManager, -) -> anyhow::Result<()> { - let cache_tx_receipts_future = streamer_message - .shards - .iter() - .filter_map(|shard| shard.chunk.as_ref()) - .map(|chunk| cache_receipts_from_tx(chunk.transactions.as_ref(), redis_connection_manager)); - - try_join_all(cache_tx_receipts_future).await?; - - let receipt_execution_outcomes: Vec = streamer_message - .shards - .iter() - .flat_map(|shard| shard.receipt_execution_outcomes.clone()) - .collect(); - - cache_receipts_from_outcomes(&receipt_execution_outcomes, redis_connection_manager).await?; - - Ok(()) -} - -async fn cache_receipts_from_tx( - transactions: &[IndexerTransactionWithOutcome], - redis_connection_manager: &storage::ConnectionManager, -) -> anyhow::Result<()> { - let push_receipt_to_watching_list_future = transactions.iter().map(|tx| async { - let transaction_hash_string = tx.transaction.hash.to_string(); - let converted_into_receipt_id = tx - .outcome - .execution_outcome - .outcome - .receipt_ids - .first() - .expect("`receipt_ids` must contain one Receipt ID") - .to_string(); - - let cache_value = CacheValue { - transaction_hash: transaction_hash_string, - parent_receipt_id: None, - children_receipt_ids: vec![], - }; - - storage::push_receipt_to_watching_list( - redis_connection_manager, - &converted_into_receipt_id, - &cache_value.try_to_vec().unwrap(), - ) - .await - }); - try_join_all(push_receipt_to_watching_list_future).await?; - - Ok(()) -} - -async fn cache_receipts_from_outcomes( - receipt_execution_outcomes: &[IndexerExecutionOutcomeWithReceipt], - redis_connection_manager: &storage::ConnectionManager, -) -> anyhow::Result<()> { - let cache_futures = receipt_execution_outcomes - .iter() - .map(|receipt_execution_outcome| { - cache_receipts_from_execution_outcome( - receipt_execution_outcome, - redis_connection_manager, - ) - }); - - try_join_all(cache_futures).await?; - Ok(()) -} - -async fn cache_receipts_from_execution_outcome( - receipt_execution_outcome: &IndexerExecutionOutcomeWithReceipt, - redis_connection_manager: &storage::ConnectionManager, -) -> anyhow::Result<()> { - let receipt_id = &receipt_execution_outcome.receipt.receipt_id.to_string(); - if let Ok(Some(cache_value_bytes)) = - storage::get::>>(redis_connection_manager, &receipt_id).await - { - // Add the newly produced receipt_ids to the watching list - let mut children_receipt_ids: Vec = receipt_execution_outcome - .execution_outcome - .outcome - .receipt_ids - .iter() - .map(ToString::to_string) - .collect(); - - // Add the success receipt to the watching list - if let ExecutionStatusView::SuccessReceiptId(receipt_id) = - receipt_execution_outcome.execution_outcome.outcome.status - { - children_receipt_ids.push(receipt_id.to_string()); - } - - if !children_receipt_ids.is_empty() { - // Rewrite CacheValue - let mut cache_value = CacheValue::try_from_slice(&cache_value_bytes)?; - cache_value.children_receipt_ids = children_receipt_ids.clone(); - storage::push_receipt_to_watching_list( - redis_connection_manager, - receipt_id, - &cache_value.try_to_vec().unwrap(), - ) - .await?; - - let push_receipt_to_watching_list_future = - children_receipt_ids.iter().map(|receipt_id_string| async { - let cache_value_bytes = CacheValue { - parent_receipt_id: Some(receipt_id.to_string()), - transaction_hash: cache_value.transaction_hash.clone(), - children_receipt_ids: vec![], - } - .try_to_vec() - .expect("Failed to BorshSerialize CacheValue"); - storage::push_receipt_to_watching_list( - redis_connection_manager, - receipt_id_string, - &cache_value_bytes, - ) - .await - }); - try_join_all(push_receipt_to_watching_list_future).await?; - } - } - Ok(()) -} - -pub(crate) async fn get_balance_retriable( - account_id: &types::AccountId, - block_hash: &str, - balance_cache: &crate::BalanceCache, - json_rpc_client: &near_jsonrpc_client::JsonRpcClient, -) -> anyhow::Result { - let mut interval = crate::INTERVAL; - let mut retry_attempt = 0usize; - - loop { - if retry_attempt == crate::RETRY_COUNT { - anyhow::bail!( - "Failed to perform query to RPC after {} attempts. Stop trying.\nAccount {}, block_hash {}", - crate::RETRY_COUNT, - account_id.to_string(), - block_hash.to_string() - ); - } - retry_attempt += 1; - - match get_balance(account_id, block_hash, balance_cache, json_rpc_client).await { - Ok(res) => return Ok(res), - Err(err) => { - tracing::error!( - target: crate::INDEXER, - "Failed to request account view details from RPC for account {}, block_hash {}.{}\n Retrying in {} milliseconds...", - account_id.to_string(), - block_hash.to_string(), - err, - interval.as_millis(), - ); - tokio::time::sleep(interval).await; - if interval < crate::MAX_DELAY_TIME { - interval *= 2; - } - } - } - } -} - -async fn get_balance( - account_id: &types::AccountId, - block_hash: &str, - balance_cache: &crate::BalanceCache, - json_rpc_client: &near_jsonrpc_client::JsonRpcClient, -) -> anyhow::Result { - let mut balances_cache_lock = balance_cache.lock().await; - let result = match balances_cache_lock.cache_get(account_id) { - None => { - let account_balance = - match get_account_view(json_rpc_client, account_id, block_hash).await { - Ok(account_view) => Ok(crate::BalanceDetails { - non_staked: account_view.amount, - staked: account_view.locked, - }), - Err(err) => match err.handler_error() { - Some(RpcQueryError::UnknownAccount { .. }) => Ok(crate::BalanceDetails { - non_staked: 0, - staked: 0, - }), - _ => Err(err.into()), - }, - }; - if let Ok(balance) = account_balance { - balances_cache_lock.cache_set(account_id.clone(), balance); - } - account_balance - } - Some(balance) => Ok(*balance), - }; - drop(balances_cache_lock); - result -} - -pub(crate) async fn save_latest_balance( - account_id: types::AccountId, - balance: &crate::BalanceDetails, - balance_cache: &crate::BalanceCache, -) { - let mut balances_cache_lock = balance_cache.lock().await; - balances_cache_lock.cache_set( - account_id, - crate::BalanceDetails { - non_staked: balance.non_staked, - staked: balance.staked, - }, - ); - drop(balances_cache_lock); -} - -async fn get_account_view( - json_rpc_client: &near_jsonrpc_client::JsonRpcClient, - account_id: &types::AccountId, - block_hash: &str, -) -> Result> { - let query = near_jsonrpc_client::methods::query::RpcQueryRequest { - block_reference: types::BlockReference::BlockId(types::BlockId::Hash( - CryptoHash::from_str(block_hash).unwrap(), - )), - request: views::QueryRequest::ViewAccount { - account_id: account_id.clone(), - }, - }; - - let account_response = json_rpc_client.call(query).await?; - match account_response.kind { - near_jsonrpc_primitives::types::query::QueryResponseKind::ViewAccount(account) => { - Ok(account) - } - _ => unreachable!( - "Unreachable code! Asked for ViewAccount (block_hash {}, account_id {})\nReceived\n\ - {:#?}\nReport this to https://github.com/near/near-jsonrpc-client-rs", - block_hash.to_string(), - account_id.to_string(), - account_response.kind - ), - } -} diff --git a/indexer/queryapi_coordinator/src/historical_block_processing.rs b/indexer/queryapi_coordinator/src/historical_block_processing.rs index 366fe7da8..ca7125d7c 100644 --- a/indexer/queryapi_coordinator/src/historical_block_processing.rs +++ b/indexer/queryapi_coordinator/src/historical_block_processing.rs @@ -25,14 +25,20 @@ pub const MAX_RPC_BLOCKS_TO_PROCESS: u8 = 20; pub fn spawn_historical_message_thread( block_height: BlockHeight, new_indexer_function: &IndexerFunction, + redis_connection_manager: &storage::ConnectionManager, ) -> Option> { + let redis_connection_manager = redis_connection_manager.clone(); new_indexer_function.start_block_height.map(|_| { let new_indexer_function_copy = new_indexer_function.clone(); - tokio::spawn(process_historical_messages_or_handle_error( - block_height, - new_indexer_function_copy, - Opts::parse(), - )) + tokio::spawn(async move { + process_historical_messages_or_handle_error( + block_height, + new_indexer_function_copy, + Opts::parse(), + &redis_connection_manager, + ) + .await + }) }) } @@ -40,8 +46,16 @@ pub(crate) async fn process_historical_messages_or_handle_error( block_height: BlockHeight, indexer_function: IndexerFunction, opts: Opts, + redis_connection_manager: &storage::ConnectionManager, ) -> i64 { - match process_historical_messages(block_height, indexer_function, opts).await { + match process_historical_messages( + block_height, + indexer_function, + opts, + redis_connection_manager, + ) + .await + { Ok(block_difference) => block_difference, Err(err) => { // todo: when Coordinator can send log messages to Runner, send this error to Runner @@ -58,6 +72,7 @@ pub(crate) async fn process_historical_messages( block_height: BlockHeight, indexer_function: IndexerFunction, opts: Opts, + redis_connection_manager: &storage::ConnectionManager, ) -> anyhow::Result { let start_block = indexer_function.start_block_height.unwrap(); let block_difference: i64 = (block_height - start_block) as i64; @@ -124,7 +139,30 @@ pub(crate) async fn process_historical_messages( blocks_from_index.append(&mut blocks_between_indexed_and_current_block); let first_block_in_index = *blocks_from_index.first().unwrap_or(&start_block); + + if !blocks_from_index.is_empty() { + storage::sadd( + redis_connection_manager, + storage::STREAMS_SET_KEY, + storage::generate_historical_stream_key(&indexer_function.get_full_name()), + ) + .await?; + storage::set( + redis_connection_manager, + storage::generate_historical_storage_key(&indexer_function.get_full_name()), + serde_json::to_string(&indexer_function)?, + ) + .await?; + } + for current_block in blocks_from_index { + storage::xadd( + redis_connection_manager, + storage::generate_historical_stream_key(&indexer_function.get_full_name()), + &[("block_height", current_block)], + ) + .await?; + send_execution_message( block_height, first_block_in_index, @@ -292,7 +330,25 @@ async fn filter_matching_unindexed_blocks_from_lake( for current_block in (last_indexed_block + 1)..ending_block_height { // fetch block file from S3 let key = format!("{}/block.json", normalize_block_height(current_block)); - let block = s3::fetch_text_file_from_s3(&lake_bucket, key, s3_client.clone()).await?; + let s3_result = s3::fetch_text_file_from_s3(&lake_bucket, key, s3_client.clone()).await; + + if s3_result.is_err() { + let error = s3_result.err().unwrap(); + if let Some(_) = error.downcast_ref::() { + tracing::info!( + target: crate::INDEXER, + "In manual filtering, skipping block number {} which was not found. For function {:?} {:?}", + current_block, + indexer_function.account_id, + indexer_function.function_name, + ); + continue; + } else { + bail!(error); + } + } + + let block = s3_result.unwrap(); let block_view = serde_json::from_slice::< near_lake_framework::near_indexer_primitives::views::BlockView, >(block.as_ref()) diff --git a/indexer/queryapi_coordinator/src/historical_block_processing_integration_tests.rs b/indexer/queryapi_coordinator/src/historical_block_processing_integration_tests.rs index c2ca90a3a..69ca67af9 100644 --- a/indexer/queryapi_coordinator/src/historical_block_processing_integration_tests.rs +++ b/indexer/queryapi_coordinator/src/historical_block_processing_integration_tests.rs @@ -17,14 +17,14 @@ mod tests { let lake_aws_access_key = env::var("LAKE_AWS_ACCESS_KEY").unwrap(); let lake_aws_secret_access_key = env::var("LAKE_AWS_SECRET_ACCESS_KEY").unwrap(); Opts { - redis_connection_string: "".to_string(), + redis_connection_string: env::var("REDIS_CONNECTION_STRING").unwrap(), lake_aws_access_key, lake_aws_secret_access_key, queue_aws_access_key: "".to_string(), queue_aws_secret_access_key: "".to_string(), aws_queue_region: "".to_string(), - queue_url: "".to_string(), - start_from_block_queue_url: "".to_string(), + queue_url: "MOCK".to_string(), + start_from_block_queue_url: "MOCK".to_string(), registry_contract_id: "".to_string(), port: 0, chain_id: ChainId::Mainnet(StartOptions::FromLatest), @@ -32,8 +32,8 @@ mod tests { } } - /// Parses env vars from .env, Run with - /// cargo test historical_block_processing_integration_tests::test_indexing_metadata_file -- mainnet from-latest; + /// Parses some env vars from .env, Run with + /// cargo test historical_block_processing_integration_tests::test_indexing_metadata_file; #[tokio::test] async fn test_indexing_metadata_file() { let opts = Opts::test_opts_with_aws(); @@ -47,8 +47,8 @@ mod tests { assert!(a.contains(&last_indexed_block)); } - /// Parses env vars from .env, Run with - /// cargo test historical_block_processing_integration_tests::test_process_historical_messages -- mainnet from-latest; + /// Parses some env vars from .env, Run with + /// cargo test historical_block_processing_integration_tests::test_process_historical_messages; #[tokio::test] async fn test_process_historical_messages() { opts::init_tracing(); @@ -76,6 +76,9 @@ mod tests { let opts = Opts::test_opts_with_aws(); let aws_config: &SdkConfig = &opts.lake_aws_sdk_config(); + let redis_connection_manager = storage::connect(&opts.redis_connection_string) + .await + .unwrap(); let fake_block_height = historical_block_processing::last_indexed_block_from_metadata(aws_config) .await @@ -84,13 +87,14 @@ mod tests { fake_block_height + 1, indexer_function, opts, + &redis_connection_manager, ) .await; assert!(result.unwrap() > 0); } - /// Parses env vars from .env, Run with - /// cargo test historical_block_processing_integration_tests::test_filter_matching_wildcard_blocks_from_index_files -- mainnet from-latest; + /// Parses some env vars from .env, Run with + /// cargo test historical_block_processing_integration_tests::test_filter_matching_wildcard_blocks_from_index_files; #[tokio::test] async fn test_filter_matching_wildcard_blocks_from_index_files() { let contract = "*.keypom.near"; @@ -146,8 +150,8 @@ mod tests { } } - /// Parses env vars from .env, Run with - /// cargo test historical_block_processing_integration_tests::test_filter_matching_blocks_from_index_files -- mainnet from-latest; + /// Parses some env vars from .env, Run with + /// cargo test historical_block_processing_integration_tests::test_filter_matching_blocks_from_index_files; #[tokio::test] async fn test_filter_matching_blocks_from_index_files() { let contract = "*.agency.near"; diff --git a/indexer/queryapi_coordinator/src/indexer_registry.rs b/indexer/queryapi_coordinator/src/indexer_registry.rs index 3ab2ec060..2c79cc70c 100644 --- a/indexer/queryapi_coordinator/src/indexer_registry.rs +++ b/indexer/queryapi_coordinator/src/indexer_registry.rs @@ -172,6 +172,7 @@ fn index_and_process_register_calls( crate::historical_block_processing::spawn_historical_message_thread( block_height, &mut new_indexer_function, + context.redis_connection_manager, ) { spawned_start_from_block_threads.push(thread); diff --git a/indexer/queryapi_coordinator/src/main.rs b/indexer/queryapi_coordinator/src/main.rs index 96cb059d5..28c2dc3f3 100644 --- a/indexer/queryapi_coordinator/src/main.rs +++ b/indexer/queryapi_coordinator/src/main.rs @@ -12,7 +12,6 @@ use indexer_types::{IndexerQueueMessage, IndexerRegistry}; use opts::{Opts, Parser}; use storage::{self, ConnectionManager}; -pub(crate) mod cache; mod historical_block_processing; mod indexer_reducer; mod indexer_registry; @@ -130,9 +129,6 @@ async fn handle_streamer_message( context: QueryApiContext<'_>, indexer_registry: SharedIndexerRegistry, ) -> anyhow::Result { - // build context for enriching filter matches - cache::update_all(&context.streamer_message, context.redis_connection_manager).await?; - let mut indexer_registry_locked = indexer_registry.lock().await; let indexer_functions = indexer_registry::registry_as_vec_of_indexer_functions(&indexer_registry_locked); @@ -200,16 +196,21 @@ async fn handle_streamer_message( set_provisioned_flag(&mut indexer_registry_locked, &indexer_function); } + storage::sadd( + context.redis_connection_manager, + storage::STREAMS_SET_KEY, + storage::generate_real_time_stream_key(&indexer_function.get_full_name()), + ) + .await?; storage::set( context.redis_connection_manager, - &format!("{}:storage", indexer_function.get_full_name()), + storage::generate_real_time_storage_key(&indexer_function.get_full_name()), serde_json::to_string(indexer_function)?, ) .await?; - - storage::add_to_registered_stream( + storage::xadd( context.redis_connection_manager, - &format!("{}:stream", indexer_function.get_full_name()), + storage::generate_real_time_stream_key(&indexer_function.get_full_name()), &[("block_height", block_height)], ) .await?; diff --git a/indexer/queryapi_coordinator/src/s3.rs b/indexer/queryapi_coordinator/src/s3.rs index 6c29a06c6..f88323688 100644 --- a/indexer/queryapi_coordinator/src/s3.rs +++ b/indexer/queryapi_coordinator/src/s3.rs @@ -203,10 +203,13 @@ fn file_name_date_after(start_date: DateTime, file_name: &str) -> bool { #[cfg(test)] mod tests { - use crate::historical_block_processing::INDEXED_ACTIONS_FILES_FOLDER; use crate::historical_block_processing::INDEXED_DATA_FILES_BUCKET; + use crate::historical_block_processing::{INDEXED_ACTIONS_FILES_FOLDER, LAKE_BUCKET_PREFIX}; use crate::opts::Opts; - use crate::s3::{find_index_files_by_pattern, list_s3_bucket_by_prefix}; + use crate::s3::{ + fetch_text_file_from_s3, find_index_files_by_pattern, list_s3_bucket_by_prefix, + }; + use aws_sdk_s3::{Client as S3Client, Config}; /// Parses env vars from .env, Run with /// cargo test s3::tests::list_delta_bucket -- mainnet from-latest; @@ -305,4 +308,34 @@ mod tests { let actual = super::storage_path_for_account(account); assert_eq!(expected, actual); } + + #[tokio::test] + async fn handle_key_404() { + let mut success = false; + + let opts = Opts::test_opts_with_aws(); + let s3_config: Config = + aws_sdk_s3::config::Builder::from(&opts.lake_aws_sdk_config()).build(); + + let s3_client: S3Client = S3Client::from_conf(s3_config); + + let s3_result = fetch_text_file_from_s3( + format!("{}{}", LAKE_BUCKET_PREFIX, "mainnet").as_str(), + "does_not_exist/block.json".to_string(), + s3_client, + ) + .await; + + if s3_result.is_err() { + let wrapped_error = s3_result.err().unwrap(); + let error = wrapped_error.root_cause(); + if let Some(_) = error.downcast_ref::() { + success = true; + } else { + println!("Failed to downcast error: {:?}", error); + } + } + + assert!(success); + } } diff --git a/indexer/storage/src/lib.rs b/indexer/storage/src/lib.rs index 98c88a297..6c449b6b2 100644 --- a/indexer/storage/src/lib.rs +++ b/indexer/storage/src/lib.rs @@ -2,12 +2,28 @@ pub use redis::{self, aio::ConnectionManager, FromRedisValue, ToRedisArgs}; const STORAGE: &str = "storage_alertexer"; -const STREAMS_SET_KEY: &str = "streams"; +pub const STREAMS_SET_KEY: &str = "streams"; pub async fn get_redis_client(redis_connection_str: &str) -> redis::Client { redis::Client::open(redis_connection_str).expect("can create redis client") } +pub fn generate_real_time_stream_key(prefix: &str) -> String { + format!("{}:real_time:stream", prefix) +} + +pub fn generate_real_time_storage_key(prefix: &str) -> String { + format!("{}:real_time:stream:storage", prefix) +} + +pub fn generate_historical_stream_key(prefix: &str) -> String { + format!("{}:historical:stream", prefix) +} + +pub fn generate_historical_storage_key(prefix: &str) -> String { + format!("{}:historical:stream:storage", prefix) +} + pub async fn connect(redis_connection_str: &str) -> anyhow::Result { Ok(get_redis_client(redis_connection_str) .await @@ -53,7 +69,7 @@ pub async fn get( Ok(value) } -async fn sadd( +pub async fn sadd( redis_connection_manager: &ConnectionManager, key: impl ToRedisArgs + std::fmt::Debug, value: impl ToRedisArgs + std::fmt::Debug, @@ -69,20 +85,12 @@ async fn sadd( Ok(()) } -async fn xadd( +pub async fn xadd( redis_connection_manager: &ConnectionManager, - stream_key: &str, + stream_key: impl ToRedisArgs + std::fmt::Debug, fields: &[(&str, impl ToRedisArgs + std::fmt::Debug)], ) -> anyhow::Result<()> { - tracing::debug!(target: STORAGE, "XADD: {}, {:?}", stream_key, fields); - - // TODO: Remove stream cap when we finally start processing it - redis::cmd("XTRIM") - .arg(stream_key) - .arg("MAXLEN") - .arg(100) - .query_async(&mut redis_connection_manager.clone()) - .await?; + tracing::debug!(target: STORAGE, "XADD: {:?}, {:?}", stream_key, fields); let mut cmd = redis::cmd("XADD"); cmd.arg(stream_key).arg("*"); @@ -97,16 +105,6 @@ async fn xadd( Ok(()) } -pub async fn add_to_registered_stream( - redis_connection_manager: &ConnectionManager, - key: &str, - fields: &[(&str, impl ToRedisArgs + std::fmt::Debug)], -) -> anyhow::Result<()> { - sadd(redis_connection_manager, STREAMS_SET_KEY, key).await?; - xadd(redis_connection_manager, key, fields).await?; - - Ok(()) -} /// Sets the key `receipt_id: &str` with value `transaction_hash: &str` to the Redis storage. /// Increments the counter `receipts_{transaction_hash}` by one. /// The counter holds how many Receipts related to the Transaction are in watching list diff --git a/registry/contract/src/lib.rs b/registry/contract/src/lib.rs index 5c164d347..c2d14b8c8 100644 --- a/registry/contract/src/lib.rs +++ b/registry/contract/src/lib.rs @@ -131,7 +131,7 @@ impl Default for Contract { role: Role::Owner, }, AccountRole { - account_id: AccountId::new_unchecked("pavelnear.near".to_string()), + account_id: AccountId::new_unchecked("nearpavel.near".to_string()), role: Role::Owner, }, AccountRole { @@ -150,6 +150,10 @@ impl Default for Contract { account_id: AccountId::new_unchecked("khorolets.near".to_string()), role: Role::Owner, }, + AccountRole { + account_id: AccountId::new_unchecked("darunrs.near".to_string()), + role: Role::Owner, + }, AccountRole { account_id: env::current_account_id(), role: Role::Owner, diff --git a/runner/.eslintrc.js b/runner/.eslintrc.js new file mode 100644 index 000000000..870c73b76 --- /dev/null +++ b/runner/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + parser: '@typescript-eslint/parser', + env: { + es2021: true, + node: true, + }, + overrides: [ + { + files: ['.eslintrc.js', 'jest.config.js'], + parser: 'espree', + extends: ['standard'], + rules: { + semi: ['error', 'always'], + 'comma-dangle': ['error', 'only-multiline'], + }, + }, + { + files: ['./src/**/*'], + parserOptions: { + project: './tsconfig.json', + }, + extends: [ + 'standard-with-typescript', + ], + rules: { + '@typescript-eslint/semi': ['error', 'always'], + '@typescript-eslint/comma-dangle': ['error', 'only-multiline'], + '@typescript-eslint/strict-boolean-expressions': 'off', + }, + }, + ], +}; diff --git a/runner/.gitignore b/runner/.gitignore new file mode 100644 index 000000000..7f277a13c --- /dev/null +++ b/runner/.gitignore @@ -0,0 +1,2 @@ +**/dist +/node_modules diff --git a/runner/Dockerfile b/runner/Dockerfile new file mode 100644 index 000000000..883e0499b --- /dev/null +++ b/runner/Dockerfile @@ -0,0 +1,12 @@ +FROM node:18.17 AS builder +WORKDIR /usr/src/app +COPY . . +RUN npm install +RUN npm run build + +FROM node:18.17 +WORKDIR /usr/src/app +COPY --from=builder /usr/src/app/package*.json ./ +RUN npm install --omit=dev +COPY --from=builder /usr/src/app/dist ./dist +CMD [ "npm", "run", "start:docker" ] diff --git a/runner/jest.config.js b/runner/jest.config.js new file mode 100644 index 000000000..eef6b07c6 --- /dev/null +++ b/runner/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node' +}; diff --git a/runner/package-lock.json b/runner/package-lock.json new file mode 100644 index 000000000..bb7ece948 --- /dev/null +++ b/runner/package-lock.json @@ -0,0 +1,7996 @@ +{ + "name": "redis-handler", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "redis-handler", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@near-lake/primitives": "^0.1.0", + "aws-sdk": "^2.1402.0", + "express": "^4.18.2", + "node-fetch": "^2.6.11", + "pg": "^8.11.1", + "pg-format": "^1.0.4", + "pluralize": "^8.0.0", + "prom-client": "^14.2.0", + "redis": "^4.6.7", + "verror": "^1.10.1", + "vm2": "^3.9.19" + }, + "devDependencies": { + "@types/express": "^4.17.17", + "@types/jest": "^29.5.3", + "@types/node": "^20.3.1", + "@types/node-fetch": "^2.6.4", + "@types/pg": "^8.10.2", + "@types/pg-format": "^1.0.2", + "@types/pluralize": "^0.0.29", + "@types/verror": "^1.10.6", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "eslint": "^8.46.0", + "eslint-config-prettier": "^8.9.0", + "eslint-config-standard-with-typescript": "^37.0.0", + "eslint-plugin-import": "^2.28.0", + "eslint-plugin-n": "^16.0.1", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-promise": "^6.1.1", + "jest": "^29.6.2", + "prettier": "^3.0.0", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "typescript": "^5.1.6" + }, + "engines": { + "node": "18.17" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", + "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@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.1" + }, + "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", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "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.22.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", + "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/generator/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 + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", + "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "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", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "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-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "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", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", + "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "dev": true, + "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.6", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.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==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "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", + "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/@babel/highlight/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/@babel/highlight/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/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/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/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/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/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "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", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "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.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.7", + "@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.6", + "@babel/parser": "^7.22.7", + "@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", + "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/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "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.6.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", + "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.1.tgz", + "integrity": "sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.46.0.tgz", + "integrity": "sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "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", + "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" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "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", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.2.tgz", + "integrity": "sha512-0N0yZof5hi44HAR2pPS+ikJ3nzKNoZdVu8FffRf3wy47I7Dm7etk/3KetMdRUqzVd16V4O2m2ISpNTbnIuqy1w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.2.tgz", + "integrity": "sha512-Oj+5B+sDMiMWLhPFF+4/DvHOf+U10rgvCLGPHP8Xlsy/7QxS51aU/eBngudHlJXnaWD5EohAgJ4js+T6pa+zOg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.2", + "@jest/reporters": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@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.6.2", + "jest-haste-map": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.2", + "jest-resolve-dependencies": "^29.6.2", + "jest-runner": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "jest-watcher": "^29.6.2", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.2", + "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.6.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.2.tgz", + "integrity": "sha512-AEcW43C7huGd/vogTddNNTDRpO6vQ2zaQNrttvWV18ArBx9Z56h7BIsXkNFJVOO4/kblWEQz30ckw0+L3izc+Q==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.2.tgz", + "integrity": "sha512-m6DrEJxVKjkELTVAztTLyS/7C92Y2b0VYqmDROYKLLALHn8T/04yPs70NADUYPrV3ruI+H3J0iUIuhkjp7vkfg==", + "dev": true, + "dependencies": { + "expect": "^29.6.2", + "jest-snapshot": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.2.tgz", + "integrity": "sha512-6zIhM8go3RV2IG4aIZaZbxwpOzz3ZiM23oxAlkquOIole+G6TrbeXnykxWYlqF7kz2HlBjdKtca20x9atkEQYg==", + "dev": true, + "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.6.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.2.tgz", + "integrity": "sha512-euZDmIlWjm1Z0lJ1D0f7a0/y5Kh/koLFMUBE5SUYWrmy8oNhJpbTBDAP6CxKnadcMLDoDf4waRYCe35cH6G6PA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.2", + "jest-mock": "^29.6.2", + "jest-util": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.2.tgz", + "integrity": "sha512-cjuJmNDjs6aMijCmSa1g2TNG4Lby/AeU7/02VtpW+SLcZXzOLK2GpN2nLqcFjmhy3B3AoPeQVx7BnyOf681bAw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/expect": "^29.6.2", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.2.tgz", + "integrity": "sha512-sWtijrvIav8LgfJZlrGCdN0nP2EWbakglJY49J1Y5QihcQLfy7ovyxxjJBRXMNltgt4uPtEcFmIMbVshEDfFWw==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "@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.6.2", + "jest-util": "^29.6.2", + "jest-worker": "^29.6.2", + "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", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jest/reporters/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 + }, + "node_modules/@jest/reporters/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "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", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jest/source-map/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 + }, + "node_modules/@jest/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jest/test-result": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.2.tgz", + "integrity": "sha512-3VKFXzcV42EYhMCsJQURptSqnyjqCGbtLuX5Xxb6Pm6gUf1wIRIl+mandIRGJyWKgNKYF9cnstti6Ls5ekduqw==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.2", + "@jest/types": "^29.6.1", + "@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.6.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.2.tgz", + "integrity": "sha512-GVYi6PfPwVejO7slw6IDO0qKVum5jtrJ3KoLGbgBWyr2qr4GaxFV6su+ZAjdTX75Sr1DkMFRk09r2ZVa+wtCGw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.6.2", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.2.tgz", + "integrity": "sha512-ZqCqEISr58Ce3U+buNFJYUktLJZOggfyvR+bZMaiV1e8B1SIvJbwZMrYz3gx/KAPn9EXmOmN+uB08yLCjWkQQg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.1", + "@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.6.2", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.2", + "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", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jest/transform/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 + }, + "node_modules/@jest/transform/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "@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", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": 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/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "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, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@near-lake/primitives": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@near-lake/primitives/-/primitives-0.1.0.tgz", + "integrity": "sha512-SvL6mA0SsqAz5AC2811I+cI9Mpayax8VsoRbY0Bizk5eYiGCT1u1iBBa8f1nikquDfJCEK+sBCt751Nz/xoZjw==" + }, + "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, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pkgr/utils/node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz", + "integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==", + "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", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz", + "integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz", + "integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz", + "integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz", + "integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "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==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "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, + "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", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "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, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "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, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "dev": true, + "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.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "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==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "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==", + "dev": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.3.tgz", + "integrity": "sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.3.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.1.tgz", + "integrity": "sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==", + "dev": true + }, + "node_modules/@types/node-fetch": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.4.tgz", + "integrity": "sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "node_modules/@types/pg": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.10.2.tgz", + "integrity": "sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@types/pg-format": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/pg-format/-/pg-format-1.0.2.tgz", + "integrity": "sha512-D3MEO6u3BObw3G4Xewjdx05MF5v/fiog78CedtrXe8BhONM8GvUz2dPfLWtI0BPRBoRd6anPHXe+sbrPReZouQ==", + "dev": true + }, + "node_modules/@types/pg/node_modules/pg-types": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.1.tgz", + "integrity": "sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==", + "dev": true, + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.0.1", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/pg/node_modules/postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dev": true, + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/pg/node_modules/postgres-date": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.0.1.tgz", + "integrity": "sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pluralize": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz", + "integrity": "sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==", + "dev": true + }, + "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==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": 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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "node_modules/@types/verror": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.6.tgz", + "integrity": "sha512-NNm+gdePAX1VGvPcGZCDKQZKYSiAWigKhKaz5KF94hG6f2s8de9Ow5+7AbXoeKxL8gavZfk4UquSAygOF2duEQ==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "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==", + "dev": 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", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "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/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "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/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==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.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/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==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "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/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": 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/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==", + "dev": true, + "dependencies": { + "@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", + "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": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": 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", + "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/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==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "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", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": 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", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.2.tgz", + "integrity": "sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "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/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sdk": { + "version": "2.1402.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1402.0.tgz", + "integrity": "sha512-ndyx3H+crHPIXJF+SO1dqzzBmQwMdoB9uCND/Ip4Ozfv5jse7X58LWpWucM9KBctQ8o37c8KvXjfTr14lE3ykA==", + "dependencies": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "util": "^0.12.4", + "uuid": "8.0.0", + "xml2js": "0.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/babel-jest": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.2.tgz", + "integrity": "sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.6.2", + "@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", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": 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", + "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, + "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", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": 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", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "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/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==" + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "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/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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/browserslist": { + "version": "4.21.10", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", + "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "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" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001517", + "electron-to-chromium": "^1.4.477", + "node-releases": "^2.0.13", + "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", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "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==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "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 + }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "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", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001518", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001518.tgz", + "integrity": "sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA==", + "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" + } + ] + }, + "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/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/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=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 + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/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": ">=0.10.0" + } + }, + "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, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "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": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "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/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "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==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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": { + "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": ">=6.0.0" + } + }, + "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/electron-to-chromium": { + "version": "1.4.478", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.478.tgz", + "integrity": "sha512-qjTA8djMXd+ruoODDFGnRCRBpID+AAfYWCyGtYTNhsuwxI19s8q19gbjKTwRS5z/LyVf5wICaIiPQGLekmbJbA==", + "dev": true + }, + "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==", + "dev": true + }, + "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/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==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "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.1", + "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.5.0", + "safe-array-concat": "^1.0.0", + "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-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.46.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.46.0.tgz", + "integrity": "sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.1", + "@eslint/js": "^8.46.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "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.2", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.9.0.tgz", + "integrity": "sha512-+sbni7NfVXnOpnRadUA8S28AUlsZt9GjgFvABIRL9Hkn8KqNzOp+7Lw4QWtrwn20KzU3wqu1QoOj2m+7rKRqkA==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz", + "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==", + "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" + } + ], + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-config-standard-with-typescript": { + "version": "37.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-37.0.0.tgz", + "integrity": "sha512-V8I/Q1eFf9tiOuFHkbksUdWO3p1crFmewecfBtRxXdnvb71BCJx+1xAknlIRZMwZioMX3/bPtMVCZsf1+AjjOw==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^5.52.0", + "eslint-config-standard": "17.1.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.52.0", + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", + "eslint-plugin-promise": "^6.0.0", + "typescript": "*" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es-x": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz", + "integrity": "sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.1.2", + "@eslint-community/regexpp": "^4.6.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.0.tgz", + "integrity": "sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", + "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.8.0", + "has": "^1.0.3", + "is-core-module": "^2.12.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", + "object.values": "^1.1.6", + "resolve": "^1.22.3", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "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", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/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/eslint-plugin-n": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-16.0.1.tgz", + "integrity": "sha512-CDmHegJN0OF3L5cz5tATH84RPQm9kG+Yx39wIqIwPR2C0uhBGMWfbbOtetR83PQjjidA5aXMu+LEFw1jaSwvTA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "builtins": "^5.0.1", + "eslint-plugin-es-x": "^7.1.0", + "ignore": "^5.2.4", + "is-core-module": "^2.12.1", + "minimatch": "^3.1.2", + "resolve": "^1.22.2", + "semver": "^7.5.3" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "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, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.5" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz", + "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz", + "integrity": "sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==", + "dev": true, + "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.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "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": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/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, + "engines": { + "node": ">=4.0" + } + }, + "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, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.2.tgz", + "integrity": "sha512-iAErsLxJ8C+S02QbLAwgSGSezLQK+XXRDt8IuFXFpwCNw2ECmzZSmjKcCaFVp5VRMk+WAvz6h6jokzEzBFZEuA==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.6.2", + "@types/node": "*", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "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/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/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/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "engines": [ + "node >=0.6.0" + ] + }, + "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/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "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/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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, + "dependencies": { + "bser": "2.1.1" + } + }, + "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, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "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/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "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", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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, + "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", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "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/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==", + "engines": { + "node": ">= 4" + } + }, + "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": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "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", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "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": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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/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": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "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/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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": { + "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/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "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": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": 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", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "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-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=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, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "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", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "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", + "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/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "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, + "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.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.2.tgz", + "integrity": "sha512-8eQg2mqFbaP7CwfsTpCxQ+sHzw1WuNWL5UUvjnWP4hx2riGz9fPSzYOaU5q8/GqWn1TfgZIVTqYJygbGbWAANg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.6.2", + "@jest/types": "^29.6.1", + "import-local": "^3.0.2", + "jest-cli": "^29.6.2" + }, + "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", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": 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", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": 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", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=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, + "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", + "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/jest-circus": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.2.tgz", + "integrity": "sha512-G9mN+KOYIUe2sB9kpJkO9Bk18J4dTDArNFPwoZ7WKHKel55eKIS/u2bLthxgojwlf9NLCVQfgzM/WsOVvoC6Fw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/expect": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.6.2", + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", + "p-limit": "^3.1.0", + "pretty-format": "^29.6.2", + "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.6.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.2.tgz", + "integrity": "sha512-TT6O247v6dCEX2UGHGyflMpxhnrL0DNqP2fRTKYm3nJJpCTfXX3GCMQPGFjXDoj0i5/Blp3jriKXFgdfmbYB6Q==", + "dev": true, + "dependencies": { + "@jest/core": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "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.6.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.2.tgz", + "integrity": "sha512-VxwFOC8gkiJbuodG9CPtMRjBUNZEHxwfQXmIudSTzFWxaci3Qub1ddTRbFNQlD/zUeaifLndh/eDccFX4wCMQw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.6.2", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.2", + "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.2", + "jest-environment-node": "^29.6.2", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.2", + "jest-runner": "^29.6.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.6.2", + "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.6.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.2.tgz", + "integrity": "sha512-t+ST7CB9GX5F2xKwhwCf0TAR17uNDiaPTZnVymP9lw0lssa9vG+AFyDZoeIHStU3WowFFwT+ky+er0WVl2yGhA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.2.tgz", + "integrity": "sha512-MsrsqA0Ia99cIpABBc3izS1ZYoYfhIy0NNWqPSE0YXbQjwchyt6B1HD2khzyPe1WiJA7hbxXy77ZoUQxn8UlSw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.6.2", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.2.tgz", + "integrity": "sha512-YGdFeZ3T9a+/612c5mTQIllvWkddPbYcN2v95ZH24oWMbGA4GGS2XdIF92QMhUhvrjjuQWYgUGW2zawOyH63MQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/fake-timers": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.2", + "jest-util": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.2.tgz", + "integrity": "sha512-+51XleTDAAysvU8rT6AnS1ZJ+WHVNqhj1k6nTvN2PYP+HjU3kqlaKQ1Lnw3NYW3bm2r8vq82X0Z1nDDHZMzHVA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@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.6.2", + "jest-worker": "^29.6.2", + "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.6.2", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.2.tgz", + "integrity": "sha512-aNqYhfp5uYEO3tdWMb2bfWv6f0b4I0LOxVRpnRLAeque2uqOVVMLh6khnTcE2qJ5wAKop0HcreM1btoysD6bPQ==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.2.tgz", + "integrity": "sha512-4LiAk3hSSobtomeIAzFTe+N8kL6z0JtF3n6I4fg29iIW7tt99R7ZcIFW34QkX+DuVrf+CUe6wuVOpm7ZKFJzZQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.6.2", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.2.tgz", + "integrity": "sha512-vnIGYEjoPSuRqV8W9t+Wow95SDp6KPX2Uf7EoeG9G99J2OVh7OSwpS4B6J0NfpEIpfkBNHlBZpA2rblEuEFhZQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.2", + "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.6.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.2.tgz", + "integrity": "sha512-hoSv3lb3byzdKfwqCuT6uTscan471GUECqgNYykg6ob0yiAw3zYc7OrPnI9Qv8Wwoa4lC7AZ9hyS4AiIx5U2zg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.2.tgz", + "integrity": "sha512-G/iQUvZWI5e3SMFssc4ug4dH0aZiZpsDq9o1PtXTV1210Ztyb2+w+ZgQkB3iOiC5SmAEzJBOHWz6Hvrd+QnNPw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.2", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.6.2", + "jest-validate": "^29.6.2", + "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.6.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.2.tgz", + "integrity": "sha512-LGqjDWxg2fuQQm7ypDxduLu/m4+4Lb4gczc13v51VMZbVP5tSBILqVx8qfWcsdP8f0G7aIqByIALDB0R93yL+w==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.2.tgz", + "integrity": "sha512-wXOT/a0EspYgfMiYHxwGLPCZfC0c38MivAlb2lMEAlwHINKemrttu1uSbcGbfDV31sFaPWnWJPmb2qXM8pqZ4w==", + "dev": true, + "dependencies": { + "@jest/console": "^29.6.2", + "@jest/environment": "^29.6.2", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.6.2", + "jest-haste-map": "^29.6.2", + "jest-leak-detector": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-resolve": "^29.6.2", + "jest-runtime": "^29.6.2", + "jest-util": "^29.6.2", + "jest-watcher": "^29.6.2", + "jest-worker": "^29.6.2", + "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-runtime": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.2.tgz", + "integrity": "sha512-2X9dqK768KufGJyIeLmIzToDmsN0m7Iek8QNxRSI/2+iPFYHF0jTwlO3ftn7gdKd98G/VQw9XJCk77rbTGZnJg==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.6.2", + "@jest/fake-timers": "^29.6.2", + "@jest/globals": "^29.6.2", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "@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.2", + "jest-message-util": "^29.6.2", + "jest-mock": "^29.6.2", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.2", + "jest-snapshot": "^29.6.2", + "jest-util": "^29.6.2", + "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", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.2.tgz", + "integrity": "sha512-1OdjqvqmRdGNvWXr/YZHuyhh5DeaLp1p/F8Tht/MrMw4Kr1Uu/j4lRG+iKl1DAqUJDWxtQBMk41Lnf/JETYBRA==", + "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/types": "^7.3.3", + "@jest/expect-utils": "^29.6.2", + "@jest/transform": "^29.6.2", + "@jest/types": "^29.6.1", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.6.2", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.6.2", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.2", + "jest-message-util": "^29.6.2", + "jest-util": "^29.6.2", + "natural-compare": "^1.4.0", + "pretty-format": "^29.6.2", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.2.tgz", + "integrity": "sha512-3eX1qb6L88lJNCFlEADKOkjpXJQyZRiavX1INZ4tRnrBVr2COd3RgcTLyUiEXMNBlDU/cgYq6taUS0fExrWW4w==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@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.6.2", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.2.tgz", + "integrity": "sha512-vGz0yMN5fUFRRbpJDPwxMpgSXW1LDKROHfBopAvDcmD6s+B/s8WJrwi+4bfH4SdInBA5C3P3BI19dBtKzx1Arg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.6.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.2.tgz", + "integrity": "sha512-GZitlqkMkhkefjfN/p3SJjrDaxPflqxEAv3/ik10OirZqJGYH5rPiIsgVcfof0Tdqg3shQGdEIxDBx+B4tuLzA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.6.2", + "@jest/types": "^29.6.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.6.2", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.2.tgz", + "integrity": "sha512-l3ccBOabTdkng8I/ORCkADz4eSMKejTYv1vB/Z83UiubqhC1oQ5Li6dWCyqOIvSifGjUBxuvxvlm6KGK2DtuAQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.6.2", + "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", + "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/jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", + "engines": { + "node": ">= 0.6.0" + } + }, + "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/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/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/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 + }, + "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/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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, + "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, + "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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "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, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "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", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "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 + }, + "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, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "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" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "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/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/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "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 + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "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==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "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/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "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/npm-run-path/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/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "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.fromentries": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", + "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "dev": true, + "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/object.groupby": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.0.tgz", + "integrity": "sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.21.2", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "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/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "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/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "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-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, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": 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/parseurl": { + "version": "1.3.3", + "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", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "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/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pg": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.1.tgz", + "integrity": "sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.6.1", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.1.tgz", + "integrity": "sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==" + }, + "node_modules/pg-format": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz", + "integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "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 + }, + "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/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "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, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=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, + "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", + "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" + }, + "engines": { + "node": ">=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, + "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", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-range": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.3.tgz", + "integrity": "sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.2.tgz", + "integrity": "sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "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", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prom-client": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", + "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", + "dependencies": { + "tdigest": "^0.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==" + }, + "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": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "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/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 + }, + "node_modules/redis": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", + "integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==", + "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/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "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/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.3.tgz", + "integrity": "sha512-P8ur/gp/AmbEzjr729bZnLjXK5Z+4P0zhIJgBgzqRih7hL7BOukHGtSTA3ACMY467GRFz3duQsi0bDZdR7DKdw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.12.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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, + "engines": { + "node": ">=10" + } + }, + "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, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": 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/run-applescript/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, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/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/run-applescript/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/run-applescript/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/run-applescript/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/run-applescript/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/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" + }, + "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==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "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/serve-static": { + "version": "1.15.0", + "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", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "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", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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/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, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "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/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.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, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "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", + "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/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": ">=4" + } + }, + "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/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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "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/synckit/node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "dependencies": { + "bintrees": "1.0.2" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "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", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "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", + "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", + "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/toidentifier": { + "version": "1.0.1", + "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", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "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.5.3", + "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-jest/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "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/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "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/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": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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": ">=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==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "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/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "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", + "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", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "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" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js/node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", + "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "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, + "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", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/v8-to-istanbul/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 + }, + "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": 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", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/vm2": { + "version": "3.9.19", + "resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.19.tgz", + "integrity": "sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==", + "dependencies": { + "acorn": "^8.7.0", + "acorn-walk": "^8.2.0" + }, + "bin": { + "vm2": "bin/vm2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "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/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "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" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "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.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "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/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "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" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "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/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/runner/package.json b/runner/package.json new file mode 100644 index 000000000..6d14e3e5c --- /dev/null +++ b/runner/package.json @@ -0,0 +1,58 @@ +{ + "name": "redis-handler", + "version": "1.0.0", + "description": "", + "main": "index.js", + "engines": { + "node": "18.17" + }, + "scripts": { + "build": "rm -rf ./dist && tsc", + "start": "npm run build && node dist/index.js", + "start:dev": "ts-node ./src/index.ts", + "start:docker": "node dist/index.js", + "test": "node --experimental-vm-modules ./node_modules/.bin/jest", + "lint": "eslint -c .eslintrc.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/express": "^4.17.17", + "@types/jest": "^29.5.3", + "@types/node": "^20.3.1", + "@types/node-fetch": "^2.6.4", + "@types/pg": "^8.10.2", + "@types/pg-format": "^1.0.2", + "@types/pluralize": "^0.0.29", + "@types/verror": "^1.10.6", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.62.0", + "eslint": "^8.46.0", + "eslint-config-prettier": "^8.9.0", + "eslint-config-standard-with-typescript": "^37.0.0", + "eslint-plugin-import": "^2.28.0", + "eslint-plugin-n": "^16.0.1", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-promise": "^6.1.1", + "jest": "^29.6.2", + "prettier": "^3.0.0", + "ts-jest": "^29.1.1", + "ts-node": "^10.9.1", + "typescript": "^5.1.6" + }, + "dependencies": { + "@near-lake/primitives": "^0.1.0", + "aws-sdk": "^2.1402.0", + "express": "^4.18.2", + "node-fetch": "^2.6.11", + "node-sql-parser": "^4.10.0", + "pg": "^8.11.1", + "pg-format": "^1.0.4", + "pluralize": "^8.0.0", + "prom-client": "^14.2.0", + "redis": "^4.6.7", + "verror": "^1.10.1", + "vm2": "^3.9.19" + } +} diff --git a/runner/src/dml-handler/dml-handler.test.ts b/runner/src/dml-handler/dml-handler.test.ts new file mode 100644 index 000000000..ab340f7f8 --- /dev/null +++ b/runner/src/dml-handler/dml-handler.test.ts @@ -0,0 +1,149 @@ +import pgFormat from 'pg-format'; +import DmlHandler from './dml-handler'; + +describe('DML Handler tests', () => { + const hasuraClient: any = { + getDbConnectionParameters: jest.fn().mockReturnValue({ + database: 'test_near', + host: 'postgres', + password: 'test_pass', + port: 5432, + username: 'test_near' + }) + }; + let PgClient: any; + let query: any; + + const ACCOUNT = 'test_near'; + const SCHEMA = 'test_schema'; + const TABLE_NAME = 'test_table'; + + beforeEach(() => { + query = jest.fn().mockReturnValue({ rows: [] }); + PgClient = jest.fn().mockImplementation(() => { + return { query, format: pgFormat }; + }); + }); + + test('Test valid insert one with array', async () => { + const inputObj = { + account_id: 'test_acc_near', + block_height: 999, + block_timestamp: 'UTC', + content: 'test_content', + receipt_id: 111, + accounts_liked: JSON.stringify(['cwpuzzles.near', 'devbose.near']) + }; + + const dmlHandler = await DmlHandler.create(ACCOUNT, hasuraClient, PgClient); + + await dmlHandler.insert(SCHEMA, TABLE_NAME, [inputObj]); + expect(query.mock.calls).toEqual([ + ['INSERT INTO test_schema."test_table" (account_id, block_height, block_timestamp, content, receipt_id, accounts_liked) VALUES (\'test_acc_near\', \'999\', \'UTC\', \'test_content\', \'111\', \'["cwpuzzles.near","devbose.near"]\') RETURNING *', []] + ]); + }); + + test('Test valid insert multiple rows with array', async () => { + const inputObj = [{ + account_id: 'morgs_near', + block_height: 1, + receipt_id: 'abc', + }, + { + account_id: 'morgs_near', + block_height: 2, + receipt_id: 'abc', + }]; + + const dmlHandler = await DmlHandler.create(ACCOUNT, hasuraClient, PgClient); + + await dmlHandler.insert(SCHEMA, TABLE_NAME, inputObj); + expect(query.mock.calls).toEqual([ + ['INSERT INTO test_schema."test_table" (account_id, block_height, receipt_id) VALUES (\'morgs_near\', \'1\', \'abc\'), (\'morgs_near\', \'2\', \'abc\') RETURNING *', []] + ]); + }); + + test('Test valid select on two fields', async () => { + const inputObj = { + account_id: 'test_acc_near', + block_height: 999, + }; + + const dmlHandler = await DmlHandler.create(ACCOUNT, hasuraClient, PgClient); + + await dmlHandler.select(SCHEMA, TABLE_NAME, inputObj); + expect(query.mock.calls).toEqual([ + ['SELECT * FROM test_schema."test_table" WHERE account_id=$1 AND block_height=$2', Object.values(inputObj)] + ]); + }); + + test('Test valid select on two fields with limit', async () => { + const inputObj = { + account_id: 'test_acc_near', + block_height: 999, + }; + + const dmlHandler = await DmlHandler.create(ACCOUNT, hasuraClient, PgClient); + + await dmlHandler.select(SCHEMA, TABLE_NAME, inputObj, 1); + expect(query.mock.calls).toEqual([ + ['SELECT * FROM test_schema."test_table" WHERE account_id=$1 AND block_height=$2 LIMIT 1', Object.values(inputObj)] + ]); + }); + + test('Test valid update on two fields', async () => { + const whereObj = { + account_id: 'test_acc_near', + block_height: 999, + }; + + const updateObj = { + content: 'test_content', + receipt_id: 111, + }; + + const dmlHandler = await DmlHandler.create(ACCOUNT, hasuraClient, PgClient); + + await dmlHandler.update(SCHEMA, TABLE_NAME, whereObj, updateObj); + expect(query.mock.calls).toEqual([ + ['UPDATE test_schema."test_table" SET content=$1, receipt_id=$2 WHERE account_id=$3 AND block_height=$4 RETURNING *', [...Object.values(updateObj), ...Object.values(whereObj)]] + ]); + }); + + test('Test valid upsert on two fields', async () => { + const inputObj = [{ + account_id: 'morgs_near', + block_height: 1, + receipt_id: 'abc' + }, + { + account_id: 'morgs_near', + block_height: 2, + receipt_id: 'abc' + }]; + + const conflictCol = ['account_id', 'block_height']; + const updateCol = ['receipt_id']; + + const dmlHandler = await DmlHandler.create(ACCOUNT, hasuraClient, PgClient); + + await dmlHandler.upsert(SCHEMA, TABLE_NAME, inputObj, conflictCol, updateCol); + expect(query.mock.calls).toEqual([ + ['INSERT INTO test_schema."test_table" (account_id, block_height, receipt_id) VALUES (\'morgs_near\', \'1\', \'abc\'), (\'morgs_near\', \'2\', \'abc\') ON CONFLICT (account_id, block_height) DO UPDATE SET receipt_id = excluded.receipt_id RETURNING *', []] + ]); + }); + + test('Test valid delete on two fields', async () => { + const inputObj = { + account_id: 'test_acc_near', + block_height: 999, + }; + + const dmlHandler = await DmlHandler.create(ACCOUNT, hasuraClient, PgClient); + + await dmlHandler.delete(SCHEMA, TABLE_NAME, inputObj); + expect(query.mock.calls).toEqual([ + ['DELETE FROM test_schema."test_table" WHERE account_id=$1 AND block_height=$2 RETURNING *', Object.values(inputObj)] + ]); + }); +}); diff --git a/runner/src/dml-handler/dml-handler.ts b/runner/src/dml-handler/dml-handler.ts new file mode 100644 index 000000000..15873e14f --- /dev/null +++ b/runner/src/dml-handler/dml-handler.ts @@ -0,0 +1,108 @@ +import { wrapError } from '../utility'; +import PgClientModule from '../pg-client'; +import HasuraClient from '../hasura-client/hasura-client'; + +export default class DmlHandler { + validTableNameRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/; + + private constructor ( + private readonly pgClient: PgClientModule, + ) {} + + static async create ( + account: string, + hasuraClient: HasuraClient = new HasuraClient(), + PgClient = PgClientModule + ): Promise { + const connectionParameters = await hasuraClient.getDbConnectionParameters(account); + const pgClient = new PgClient({ + user: connectionParameters.username, + password: connectionParameters.password, + host: process.env.PGHOST, + port: Number(connectionParameters.port), + database: connectionParameters.database, + }); + + return new DmlHandler(pgClient); + } + + async insert (schemaName: string, tableName: string, objects: any[]): Promise { + if (!objects?.length) { + return []; + } + + const keys = Object.keys(objects[0]); + // Get array of values from each object, and return array of arrays as result. Expects all objects to have the same number of items in same order + const values = objects.map(obj => keys.map(key => obj[key])); + const query = `INSERT INTO ${schemaName}."${tableName}" (${keys.join(', ')}) VALUES %L RETURNING *`; + + const result = await wrapError(async () => await this.pgClient.query(this.pgClient.format(query, values), []), `Failed to execute '${query}' on ${schemaName}."${tableName}".`); + if (result.rows?.length === 0) { + console.log('No rows were inserted.'); + } + return result.rows; + } + + async select (schemaName: string, tableName: string, object: any, limit: number | null = null): Promise { + const keys = Object.keys(object); + const values = Object.values(object); + const param = Array.from({ length: keys.length }, (_, index) => `${keys[index]}=$${index + 1}`).join(' AND '); + let query = `SELECT * FROM ${schemaName}."${tableName}" WHERE ${param}`; + if (limit !== null) { + query = query.concat(' LIMIT ', Math.round(limit).toString()); + } + + const result = await wrapError(async () => await this.pgClient.query(this.pgClient.format(query), values), `Failed to execute '${query}' on ${schemaName}."${tableName}".`); + if (!(result.rows && result.rows.length > 0)) { + console.log('No rows were selected.'); + } + return result.rows; + } + + async update (schemaName: string, tableName: string, whereObject: any, updateObject: any): Promise { + const updateKeys = Object.keys(updateObject); + const updateParam = Array.from({ length: updateKeys.length }, (_, index) => `${updateKeys[index]}=$${index + 1}`).join(', '); + const whereKeys = Object.keys(whereObject); + const whereParam = Array.from({ length: whereKeys.length }, (_, index) => `${whereKeys[index]}=$${index + 1 + updateKeys.length}`).join(' AND '); + + const queryValues = [...Object.values(updateObject), ...Object.values(whereObject)]; + const query = `UPDATE ${schemaName}."${tableName}" SET ${updateParam} WHERE ${whereParam} RETURNING *`; + + const result = await wrapError(async () => await this.pgClient.query(this.pgClient.format(query), queryValues), `Failed to execute '${query}' on ${schemaName}."${tableName}".`); + if (!(result.rows && result.rows.length > 0)) { + console.log('No rows were selected.'); + } + return result.rows; + } + + async upsert (schemaName: string, tableName: string, objects: any[], conflictColumns: string[], updateColumns: string[]): Promise { + if (!objects?.length) { + return []; + } + + const keys = Object.keys(objects[0]); + // Get array of values from each object, and return array of arrays as result. Expects all objects to have the same number of items in same order + const values = objects.map(obj => keys.map(key => obj[key])); + const updatePlaceholders = updateColumns.map(col => `${col} = excluded.${col}`).join(', '); + const query = `INSERT INTO ${schemaName}."${tableName}" (${keys.join(', ')}) VALUES %L ON CONFLICT (${conflictColumns.join(', ')}) DO UPDATE SET ${updatePlaceholders} RETURNING *`; + + const result = await wrapError(async () => await this.pgClient.query(this.pgClient.format(query, values), []), `Failed to execute '${query}' on ${schemaName}."${tableName}".`); + if (result.rows?.length === 0) { + console.log('No rows were inserted or updated.'); + } + return result.rows; + } + + async delete (schemaName: string, tableName: string, object: any): Promise { + const keys = Object.keys(object); + const values = Object.values(object); + const param = Array.from({ length: keys.length }, (_, index) => `${keys[index]}=$${index + 1}`).join(' AND '); + const query = `DELETE FROM ${schemaName}."${tableName}" WHERE ${param} RETURNING *`; + + const result = await wrapError(async () => await this.pgClient.query(this.pgClient.format(query), values), `Failed to execute '${query}' on ${schemaName}."${tableName}".`); + if (!(result.rows && result.rows.length > 0)) { + console.log('No rows were deleted.'); + } + return result.rows; + } +} diff --git a/runner/src/dml-handler/index.ts b/runner/src/dml-handler/index.ts new file mode 100644 index 000000000..8beb5a70c --- /dev/null +++ b/runner/src/dml-handler/index.ts @@ -0,0 +1 @@ +export { default } from './dml-handler'; diff --git a/runner/src/globals.d.ts b/runner/src/globals.d.ts new file mode 100644 index 000000000..efc7733ad --- /dev/null +++ b/runner/src/globals.d.ts @@ -0,0 +1,12 @@ +declare namespace NodeJS { + export interface ProcessEnv { + HASURA_ENDPOINT: string + HASURA_ADMIN_SECRET: string + PGHOST: string + PGPORT: string + PGUSER: string + PGPASSWORD: string + PGDATABASE: string + PORT: string + } +} diff --git a/runner/src/hasura-client/__snapshots__/hasura-client.test.ts.snap b/runner/src/hasura-client/__snapshots__/hasura-client.test.ts.snap new file mode 100644 index 000000000..231391337 --- /dev/null +++ b/runner/src/hasura-client/__snapshots__/hasura-client.test.ts.snap @@ -0,0 +1,406 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HasuraClient adds a datasource 1`] = ` +{ + "args": { + "configuration": { + "connection_info": { + "database_url": { + "connection_parameters": { + "database": "morgs_near", + "host": "localhost", + "password": "password", + "port": 5432, + "username": "morgs_near", + }, + }, + }, + }, + "name": "morgs_near", + }, + "type": "pg_add_source", +} +`; + +exports[`HasuraClient adds the specified permissions for the specified roles/table/schema 1`] = ` +{ + "args": [ + { + "args": { + "permission": { + "allow_aggregations": true, + "check": {}, + "columns": "*", + "computed_fields": [], + "filter": {}, + }, + "role": "role", + "source": "default", + "table": { + "name": "height", + "schema": "schema", + }, + }, + "type": "pg_create_select_permission", + }, + { + "args": { + "permission": { + "check": {}, + "columns": "*", + "computed_fields": [], + "filter": {}, + }, + "role": "role", + "source": "default", + "table": { + "name": "height", + "schema": "schema", + }, + }, + "type": "pg_create_insert_permission", + }, + { + "args": { + "permission": { + "check": {}, + "columns": "*", + "computed_fields": [], + "filter": {}, + }, + "role": "role", + "source": "default", + "table": { + "name": "height", + "schema": "schema", + }, + }, + "type": "pg_create_update_permission", + }, + { + "args": { + "permission": { + "check": {}, + "columns": "*", + "computed_fields": [], + "filter": {}, + }, + "role": "role", + "source": "default", + "table": { + "name": "height", + "schema": "schema", + }, + }, + "type": "pg_create_delete_permission", + }, + { + "args": { + "permission": { + "allow_aggregations": true, + "check": {}, + "columns": "*", + "computed_fields": [], + "filter": {}, + }, + "role": "role", + "source": "default", + "table": { + "name": "width", + "schema": "schema", + }, + }, + "type": "pg_create_select_permission", + }, + { + "args": { + "permission": { + "check": {}, + "columns": "*", + "computed_fields": [], + "filter": {}, + }, + "role": "role", + "source": "default", + "table": { + "name": "width", + "schema": "schema", + }, + }, + "type": "pg_create_insert_permission", + }, + { + "args": { + "permission": { + "check": {}, + "columns": "*", + "computed_fields": [], + "filter": {}, + }, + "role": "role", + "source": "default", + "table": { + "name": "width", + "schema": "schema", + }, + }, + "type": "pg_create_update_permission", + }, + { + "args": { + "permission": { + "check": {}, + "columns": "*", + "computed_fields": [], + "filter": {}, + }, + "role": "role", + "source": "default", + "table": { + "name": "width", + "schema": "schema", + }, + }, + "type": "pg_create_delete_permission", + }, + ], + "type": "bulk", +} +`; + +exports[`HasuraClient checks if a schema exists within source 1`] = ` +[ + [ + "mock-hasura-endpoint/v2/query", + { + "body": "{"type":"run_sql","args":{"sql":"SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'schema'","read_only":true,"source":"source"}}", + "headers": { + "X-Hasura-Admin-Secret": "mock-hasura-admin-secret", + }, + "method": "POST", + }, + ], +] +`; + +exports[`HasuraClient checks if datasource exists 1`] = ` +{ + "args": {}, + "type": "export_metadata", + "version": 2, +} +`; + +exports[`HasuraClient creates a schema 1`] = ` +[ + [ + "mock-hasura-endpoint/v2/query", + { + "body": "{"type":"run_sql","args":{"sql":"CREATE schema schemaName","read_only":false,"source":"dbName"}}", + "headers": { + "X-Hasura-Admin-Secret": "mock-hasura-admin-secret", + }, + "method": "POST", + }, + ], +] +`; + +exports[`HasuraClient gets table names within a schema 1`] = ` +{ + "args": { + "source": "source", + }, + "type": "pg_get_source_tables", +} +`; + +exports[`HasuraClient runs migrations for the specified schema 1`] = ` +[ + [ + "mock-hasura-endpoint/v2/query", + { + "body": "{"type":"run_sql","args":{"sql":"\\n set schema 'schemaName';\\n CREATE TABLE blocks (height numeric)\\n ","read_only":false,"source":"dbName"}}", + "headers": { + "X-Hasura-Admin-Secret": "mock-hasura-admin-secret", + }, + "method": "POST", + }, + ], +] +`; + +exports[`HasuraClient tracks foreign key relationships 1`] = ` +{ + "args": { + "read_only": true, + "source": "source", + "sql": " + SELECT + COALESCE(json_agg(row_to_json(info)), '[]'::JSON) + FROM ( + SELECT + q.table_schema::text AS table_schema, + q.table_name::text AS table_name, + q.constraint_name::text AS constraint_name, + min(q.ref_table_table_schema::text) AS ref_table_table_schema, + min(q.ref_table::text) AS ref_table, + json_object_agg(ac.attname, afc.attname) AS column_mapping, + min(q.confupdtype::text) AS on_update, + min(q.confdeltype::text) AS + on_delete + FROM ( + SELECT + ctn.nspname AS table_schema, + ct.relname AS table_name, + r.conrelid AS table_id, + r.conname AS constraint_name, + cftn.nspname AS ref_table_table_schema, + cft.relname AS ref_table, + r.confrelid AS ref_table_id, + r.confupdtype, + r.confdeltype, + unnest(r.conkey) AS column_id, + unnest(r.confkey) AS ref_column_id + FROM + pg_constraint r + JOIN pg_class ct ON r.conrelid = ct.oid + JOIN pg_namespace ctn ON ct.relnamespace = ctn.oid + JOIN pg_class cft ON r.confrelid = cft.oid + JOIN pg_namespace cftn ON cft.relnamespace = cftn.oid + WHERE + r.contype = 'f'::"char" + AND ((ctn.nspname='public')) + ) q + JOIN pg_attribute ac ON q.column_id = ac.attnum + AND q.table_id = ac.attrelid + JOIN pg_attribute afc ON q.ref_column_id = afc.attnum + AND q.ref_table_id = afc.attrelid + GROUP BY + q.table_schema, + q.table_name, + q.constraint_name) AS info; + ", + }, + "type": "run_sql", +} +`; + +exports[`HasuraClient tracks foreign key relationships 2`] = ` +{ + "args": [ + { + "args": { + "name": "comments", + "source": "source", + "table": { + "name": "posts", + "schema": "public", + }, + "using": { + "foreign_key_constraint_on": { + "column": "post_id", + "table": { + "name": "comments", + "schema": "public", + }, + }, + }, + }, + "type": "pg_create_array_relationship", + }, + { + "args": { + "name": "post", + "source": "source", + "table": { + "name": "comments", + "schema": "public", + }, + "using": { + "foreign_key_constraint_on": "post_id", + }, + }, + "type": "pg_create_object_relationship", + }, + { + "args": { + "name": "post_likes", + "source": "source", + "table": { + "name": "posts", + "schema": "public", + }, + "using": { + "foreign_key_constraint_on": { + "column": "post_id", + "table": { + "name": "post_likes", + "schema": "public", + }, + }, + }, + }, + "type": "pg_create_array_relationship", + }, + { + "args": { + "name": "post", + "source": "source", + "table": { + "name": "post_likes", + "schema": "public", + }, + "using": { + "foreign_key_constraint_on": "post_id", + }, + }, + "type": "pg_create_object_relationship", + }, + ], + "type": "bulk", +} +`; + +exports[`HasuraClient tracks the specified tables for a specified schema 1`] = ` +{ + "args": [ + { + "args": { + "source": "source", + "table": { + "name": "height", + "schema": "schema", + }, + }, + "type": "pg_track_table", + }, + { + "args": { + "source": "source", + "table": { + "name": "width", + "schema": "schema", + }, + }, + "type": "pg_track_table", + }, + ], + "type": "bulk", +} +`; + +exports[`HasuraClient untracks the specified tables 1`] = ` +[ + [ + "mock-hasura-endpoint/v1/metadata", + { + "body": "{"type":"bulk","args":[{"type":"pg_untrack_table","args":{"table":{"schema":"schema","name":"height"},"source":"default","cascade":true}},{"type":"pg_untrack_table","args":{"table":{"schema":"schema","name":"width"},"source":"default","cascade":true}}]}", + "headers": { + "X-Hasura-Admin-Secret": "mock-hasura-admin-secret", + }, + "method": "POST", + }, + ], +] +`; diff --git a/runner/src/hasura-client/hasura-client.test.ts b/runner/src/hasura-client/hasura-client.test.ts new file mode 100644 index 000000000..d4b621886 --- /dev/null +++ b/runner/src/hasura-client/hasura-client.test.ts @@ -0,0 +1,323 @@ +import type fetch from 'node-fetch'; + +import HasuraClient from './hasura-client'; + +describe('HasuraClient', () => { + const oldEnv = process.env; + + const HASURA_ENDPOINT = 'mock-hasura-endpoint'; + const HASURA_ADMIN_SECRET = 'mock-hasura-admin-secret'; + const PGHOST = 'localhost'; + const PGPORT = '5432'; + + beforeAll(() => { + process.env = { + ...oldEnv, + HASURA_ENDPOINT, + HASURA_ADMIN_SECRET, + PGHOST, + PGPORT, + }; + }); + + afterAll(() => { + process.env = oldEnv; + }); + + it('creates a schema', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({}) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await client.createSchema('dbName', 'schemaName'); + + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + it('checks if a schema exists within source', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({ + result: [['schema_name'], ['name']] + }) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + const result = await client.doesSchemaExist('source', 'schema'); + + expect(result).toBe(true); + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + it('checks if datasource exists', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({ + metadata: { + sources: [ + { + name: 'name' + } + ] + }, + }), + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await expect(client.doesSourceExist('name')).resolves.toBe(true); + expect(mockFetch.mock.calls[0][1].headers['X-Hasura-Admin-Secret']).toBe(HASURA_ADMIN_SECRET); + expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchSnapshot(); + }); + + it('runs migrations for the specified schema', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({}) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await client.runMigrations('dbName', 'schemaName', 'CREATE TABLE blocks (height numeric)'); + + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + it('gets table names within a schema', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify([ + { name: 'table_name', schema: 'morgs_near' }, + { name: 'height', schema: 'schema' }, + { name: 'width', schema: 'schema' } + ]) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + const names = await client.getTableNames('schema', 'source'); + + expect(names).toEqual(['height', 'width']); + expect(mockFetch.mock.calls[0][1].headers['X-Hasura-Admin-Secret']).toBe(HASURA_ADMIN_SECRET); + expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchSnapshot(); + }); + + it('tracks the specified tables for a specified schema', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({}) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await client.trackTables('schema', ['height', 'width'], 'source'); + + expect(mockFetch.mock.calls[0][1].headers['X-Hasura-Admin-Secret']).toBe(HASURA_ADMIN_SECRET); + expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchSnapshot(); + }); + + it('untracks the specified tables', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({}) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await client.untrackTables('default', 'schema', ['height', 'width']); + + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + it('adds the specified permissions for the specified roles/table/schema', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({}) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await client.addPermissionsToTables('schema', 'default', ['height', 'width'], 'role', ['select', 'insert', 'update', 'delete']); + + expect(mockFetch.mock.calls[0][1].headers['X-Hasura-Admin-Secret']).toBe(HASURA_ADMIN_SECRET); + expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchSnapshot(); + }); + + it('adds a datasource', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({}) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await client.addDatasource('morgs_near', 'password', 'morgs_near'); + + expect(mockFetch.mock.calls[0][1].headers['X-Hasura-Admin-Secret']).toBe(HASURA_ADMIN_SECRET); + expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchSnapshot(); + }); + + it('tracks foreign key relationships', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({ + result: [ + [ + 'coalesce' + ], + [ + JSON.stringify([ + { + table_schema: 'public', + table_name: 'comments', + constraint_name: 'comments_post_id_fkey', + ref_table_table_schema: 'public', + ref_table: 'posts', + column_mapping: { + post_id: 'id' + }, + on_update: 'a', + on_delete: 'a' + }, + { + table_schema: 'public', + table_name: 'post_likes', + constraint_name: 'post_likes_post_id_fkey', + ref_table_table_schema: 'public', + ref_table: 'posts', + column_mapping: { + post_id: 'id' + }, + on_update: 'a', + on_delete: 'c' + } + ]) + ] + ] + }), + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await client.trackForeignKeyRelationships('public', 'source'); + + expect(mockFetch.mock.calls[0][1].headers['X-Hasura-Admin-Secret']).toBe(HASURA_ADMIN_SECRET); + expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchSnapshot(); + + expect(mockFetch.mock.calls[1][1].headers['X-Hasura-Admin-Secret']).toBe(HASURA_ADMIN_SECRET); + expect(JSON.parse(mockFetch.mock.calls[1][1].body)).toMatchSnapshot(); + }); + + it('skips foreign key tracking if none exist', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({ + result: [ + [ + 'coalesce' + ], + [ + JSON.stringify([]) + ] + ] + }), + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + + await client.trackForeignKeyRelationships('public', 'source'); + + expect(mockFetch).toBeCalledTimes(1); // to fetch the foreign keys + }); + + it('returns connection parameters for valid and invalid users', async () => { + const testUsers = { + testA_near: 'passA', + testB_near: 'passB', + testC_near: 'passC' + }; + const TEST_METADATA = generateMetadata(testUsers); + const mockFetch = jest + .fn() + .mockResolvedValue({ + status: 200, + text: () => JSON.stringify({ metadata: TEST_METADATA }) + }); + const client = new HasuraClient({ fetch: mockFetch as unknown as typeof fetch }); + const result = await client.getDbConnectionParameters('testB_near'); + expect(result).toEqual(generateConnectionParameter('testB_near', 'passB')); + await expect(client.getDbConnectionParameters('fake_near')).rejects.toThrow('Could not find connection parameters for user fake_near on respective database.'); + }); +}); + +function generateMetadata (testUsers: any): any { + const sources = []; + // Insert default source which has different format than the rest + sources.push({ + name: 'default', + kind: 'postgres', + tables: [], + configuration: { + connection_info: { + database_url: { from_env: 'HASURA_GRAPHQL_DATABASE_URL' }, + isolation_level: 'read-committed', + pool_settings: { + connection_lifetime: 600, + idle_timeout: 180, + max_connections: 50, + retries: 1 + }, + use_prepared_statements: true + } + } + }); + + Object.keys(testUsers).forEach((user) => { + sources.push(generateSource(user, testUsers[user])); + }); + + return { + version: 3, + sources + }; +} + +function generateSource (user: string, password: string): any { + return { + name: user, + kind: 'postgres', + tables: [], + configuration: { + connection_info: { + database_url: { connection_parameters: generateConnectionParameter(user, password) }, + isolation_level: 'read-committed', + use_prepared_statements: false + } + } + }; +} + +function generateConnectionParameter (user: string, password: string): any { + return { + database: user, + host: 'postgres', + password, + port: 5432, + username: user + }; +} diff --git a/runner/src/hasura-client/hasura-client.ts b/runner/src/hasura-client/hasura-client.ts new file mode 100644 index 000000000..0162c4b41 --- /dev/null +++ b/runner/src/hasura-client/hasura-client.ts @@ -0,0 +1,348 @@ +import fetch, { type Response } from 'node-fetch'; +import pluralize from 'pluralize'; + +interface Dependencies { + fetch: typeof fetch +} + +interface SqlOptions { + readOnly: boolean + source?: string +} + +type MetadataRequestArgs = Record; + +type MetadataRequests = Record; + +export default class HasuraClient { + static DEFAULT_DATABASE = 'default'; + static DEFAULT_SCHEMA = 'public'; + + private readonly deps: Dependencies; + + constructor (deps?: Partial) { + this.deps = { + fetch, + ...deps, + }; + } + + async executeSql (sql: string, opts: SqlOptions): Promise { + const response: Response = await this.deps.fetch( + `${process.env.HASURA_ENDPOINT}/v2/query`, + { + method: 'POST', + headers: { + 'X-Hasura-Admin-Secret': process.env.HASURA_ADMIN_SECRET, + }, + body: JSON.stringify({ + type: 'run_sql', + args: { + sql, + read_only: opts.readOnly, + source: opts.source ?? 'default', + }, + }), + } + ); + + const body: string = await response.text(); + + if (response.status !== 200) { + throw new Error(body); + } + + return JSON.parse(body); + } + + async executeMetadataRequest ( + type: string, + args: MetadataRequestArgs, + version?: number + ): Promise { + const response: Response = await this.deps.fetch( + `${process.env.HASURA_ENDPOINT}/v1/metadata`, + { + method: 'POST', + headers: { + 'X-Hasura-Admin-Secret': process.env.HASURA_ADMIN_SECRET, + }, + body: JSON.stringify({ + type, + args, + ...(version && { version }), + }), + } + ); + + const body: string = await response.text(); + + if (response.status !== 200) { + throw new Error(body); + } + + return JSON.parse(body); + } + + async executeBulkMetadataRequest ( + metadataRequests: MetadataRequests + ): Promise { + return await this.executeMetadataRequest('bulk', metadataRequests); + } + + async exportMetadata (): Promise { + const { metadata } = await this.executeMetadataRequest( + 'export_metadata', + {}, + 2 + ); + return metadata; + } + + async getDbConnectionParameters (account: string): Promise { + const metadata = await this.exportMetadata(); + const source = metadata.sources.find((source: { name: any, configuration: any }) => source.name === account); + if (source === undefined) { + throw new Error(`Could not find connection parameters for user ${account} on respective database.`); + } + return source.configuration.connection_info.database_url.connection_parameters; + } + + async doesSourceExist (source: string): Promise { + const metadata = await this.exportMetadata(); + return metadata.sources.filter(({ name }: { name: string }) => name === source).length > 0; + } + + async doesSchemaExist (source: string, schemaName: string): Promise { + const { result } = await this.executeSql( + `SELECT schema_name FROM information_schema.schemata WHERE schema_name = '${schemaName}'`, + { source, readOnly: true } + ); + + return result.length > 1; + } + + async createSchema (source: string, schemaName: string): Promise { + return await this.executeSql(`CREATE schema ${schemaName}`, { + source, + readOnly: false, + }); + } + + async runMigrations (source: string, schemaName: string, migration: string): Promise { + return await this.executeSql( + ` + set schema '${schemaName}'; + ${migration} + `, + { source, readOnly: false } + ); + } + + async getTableNames (schemaName: string, source: string): Promise { + const tablesInSource = await this.executeMetadataRequest( + 'pg_get_source_tables', + { + source, + } + ); + + return tablesInSource + .filter(({ schema }: { schema: string }) => schema === schemaName) + .map(({ name }: { name: string }) => name); + } + + async trackTables ( + schemaName: string, + tableNames: string[], + source: string + ): Promise { + return await this.executeBulkMetadataRequest( + tableNames.map(name => ({ + type: 'pg_track_table', + args: { + source, + table: { + name, + schema: schemaName, + }, + }, + })) + ); + } + + async untrackTables ( + source: string, + schema: string, + tableNames: string[], + cascade = true + ): Promise { + return await this.executeBulkMetadataRequest( + tableNames.map(name => ({ + type: 'pg_untrack_table', + args: { + table: { + schema, + name, + }, + source, + cascade, + }, + })) + ); + } + + async getForeignKeys (schemaName: string, source: string): Promise { + const { result } = await this.executeSql( + ` + SELECT + COALESCE(json_agg(row_to_json(info)), '[]'::JSON) + FROM ( + SELECT + q.table_schema::text AS table_schema, + q.table_name::text AS table_name, + q.constraint_name::text AS constraint_name, + min(q.ref_table_table_schema::text) AS ref_table_table_schema, + min(q.ref_table::text) AS ref_table, + json_object_agg(ac.attname, afc.attname) AS column_mapping, + min(q.confupdtype::text) AS on_update, + min(q.confdeltype::text) AS + on_delete + FROM ( + SELECT + ctn.nspname AS table_schema, + ct.relname AS table_name, + r.conrelid AS table_id, + r.conname AS constraint_name, + cftn.nspname AS ref_table_table_schema, + cft.relname AS ref_table, + r.confrelid AS ref_table_id, + r.confupdtype, + r.confdeltype, + unnest(r.conkey) AS column_id, + unnest(r.confkey) AS ref_column_id + FROM + pg_constraint r + JOIN pg_class ct ON r.conrelid = ct.oid + JOIN pg_namespace ctn ON ct.relnamespace = ctn.oid + JOIN pg_class cft ON r.confrelid = cft.oid + JOIN pg_namespace cftn ON cft.relnamespace = cftn.oid + WHERE + r.contype = 'f'::"char" + AND ((ctn.nspname='${schemaName}')) + ) q + JOIN pg_attribute ac ON q.column_id = ac.attnum + AND q.table_id = ac.attrelid + JOIN pg_attribute afc ON q.ref_column_id = afc.attnum + AND q.ref_table_id = afc.attrelid + GROUP BY + q.table_schema, + q.table_name, + q.constraint_name) AS info; + `, + { readOnly: true, source } + ); + + const [, [foreignKeysJsonString]] = result; + + return JSON.parse(foreignKeysJsonString); + } + + async trackForeignKeyRelationships ( + schemaName: string, + source: string + ): Promise { + const foreignKeys = await this.getForeignKeys(schemaName, source); + + if (foreignKeys.length === 0) { + return; + } + + return await this.executeBulkMetadataRequest( + foreignKeys + .map((foreignKey) => ([ + { + type: 'pg_create_array_relationship', + args: { + source, + name: foreignKey.table_name, + table: { + name: foreignKey.ref_table, + schema: schemaName, + }, + using: { + foreign_key_constraint_on: { + table: { + name: foreignKey.table_name, + schema: schemaName, + }, + column: Object.keys(foreignKey.column_mapping)[0], + } + }, + } + }, + { + type: 'pg_create_object_relationship', + args: { + source, + name: pluralize.singular(foreignKey.ref_table), + table: { + name: foreignKey.table_name, + schema: schemaName, + }, + using: { + foreign_key_constraint_on: Object.keys(foreignKey.column_mapping)[0], + }, + } + }, + ])) + .flat() + ); + } + + async addPermissionsToTables (schemaName: string, source: string, tableNames: string[], roleName: string, permissions: string[]): Promise { + return await this.executeBulkMetadataRequest( + tableNames + .map((tableName) => ( + permissions.map((permission) => ({ + type: `pg_create_${permission}_permission`, + args: { + source, + table: { + name: tableName, + schema: schemaName, + }, + role: roleName, + permission: { + columns: '*', + check: {}, + computed_fields: [], + filter: {}, + ...(permission === 'select' && { allow_aggregations: true }) + }, + }, + })) + )) + .flat() + ); + } + + async addDatasource (userName: string, password: string, databaseName: string): Promise { + return await this.executeMetadataRequest('pg_add_source', { + name: databaseName, + configuration: { + connection_info: { + database_url: { + connection_parameters: { + password, + database: databaseName, + username: userName, + host: process.env.PGHOST, + port: Number(process.env.PGPORT), + } + }, + }, + }, + }); + } +} diff --git a/runner/src/hasura-client/index.ts b/runner/src/hasura-client/index.ts new file mode 100644 index 000000000..be6fcaaec --- /dev/null +++ b/runner/src/hasura-client/index.ts @@ -0,0 +1 @@ +export { default } from './hasura-client'; diff --git a/runner/src/index.ts b/runner/src/index.ts new file mode 100644 index 000000000..a483a9336 --- /dev/null +++ b/runner/src/index.ts @@ -0,0 +1,39 @@ +import { startServer as startMetricsServer } from './metrics'; +import RedisClient from './redis-client'; +import StreamHandler from './stream-handler'; + +const redisClient = new RedisClient(); + +startMetricsServer().catch((err) => { + console.error('Failed to start metrics server', err); +}); + +const STREAM_HANDLER_THROTTLE_MS = 500; + +type StreamHandlers = Record; + +void (async function main () { + try { + const streamHandlers: StreamHandlers = {}; + + while (true) { + const streamKeys = await redisClient.getStreams(); + + streamKeys.forEach((streamKey) => { + if (streamHandlers[streamKey] !== undefined) { + return; + } + + const streamHandler = new StreamHandler(streamKey); + + streamHandlers[streamKey] = streamHandler; + }); + + await new Promise((resolve) => + setTimeout(resolve, STREAM_HANDLER_THROTTLE_MS), + ); + } + } finally { + await redisClient.disconnect(); + } +})(); diff --git a/runner/src/indexer/__snapshots__/indexer.test.ts.snap b/runner/src/indexer/__snapshots__/indexer.test.ts.snap new file mode 100644 index 000000000..691dcf902 --- /dev/null +++ b/runner/src/indexer/__snapshots__/indexer.test.ts.snap @@ -0,0 +1,327 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Indexer unit tests Indexer.buildContext() can fetch from the near social api 1`] = ` +[ + [ + "https://api.near.social/index", + { + "body": "{"action":"post","key":"main","options":{"limit":1,"order":"desc"}}", + "headers": { + "Content-Type": "application/json", + }, + "method": "POST", + }, + ], +] +`; + +exports[`Indexer unit tests Indexer.runFunctions() allows imperative execution of GraphQL operations 1`] = ` +[ + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){\\n insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id}\\n }","variables":{"function_name":"buildnear.testnet/test","block_height":82699904,"message":"Running function buildnear.testnet/test, lag is: NaNms from block timestamp"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n mutation SetStatus($function_name: String, $status: String) {\\n insert_indexer_state_one(object: {function_name: $function_name, status: $status, current_block_height: 0 }, on_conflict: { constraint: indexer_state_pkey, update_columns: status }) {\\n function_name\\n status\\n }\\n }\\n ","variables":{"function_name":"buildnear.testnet/test","status":"RUNNING"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n query {\\n posts(where: { id: { _eq: 1 } }) {\\n id\\n }\\n }\\n "}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "buildnear_testnet", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n mutation {\\n insert_comments(\\n objects: {account_id: \\"morgs.near\\", block_height: 82699904, content: \\"cool post\\", post_id: 1}\\n ) {\\n returning {\\n id\\n }\\n }\\n }\\n "}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "buildnear_testnet", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation WriteBlock($function_name: String!, $block_height: numeric!) {\\n insert_indexer_state(\\n objects: {current_block_height: $block_height, function_name: $function_name}\\n on_conflict: {constraint: indexer_state_pkey, update_columns: current_block_height}\\n ) {\\n returning {\\n current_block_height\\n function_name\\n }\\n }\\n }","variables":{"function_name":"buildnear.testnet/test","block_height":82699904}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], +] +`; + +exports[`Indexer unit tests Indexer.runFunctions() catches errors 1`] = ` +[ + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){\\n insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id}\\n }","variables":{"function_name":"buildnear.testnet/test","block_height":456,"message":"Running function buildnear.testnet/test, lag is: NaNms from block timestamp"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n mutation SetStatus($function_name: String, $status: String) {\\n insert_indexer_state_one(object: {function_name: $function_name, status: $status, current_block_height: 0 }, on_conflict: { constraint: indexer_state_pkey, update_columns: status }) {\\n function_name\\n status\\n }\\n }\\n ","variables":{"function_name":"buildnear.testnet/test","status":"RUNNING"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){\\n insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id}\\n }","variables":{"function_name":"buildnear.testnet/test","block_height":456,"message":"Error running IndexerFunction:boom"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n mutation SetStatus($function_name: String, $status: String) {\\n insert_indexer_state_one(object: {function_name: $function_name, status: $status, current_block_height: 0 }, on_conflict: { constraint: indexer_state_pkey, update_columns: status }) {\\n function_name\\n status\\n }\\n }\\n ","variables":{"function_name":"buildnear.testnet/test","status":"STOPPED"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], +] +`; + +exports[`Indexer unit tests Indexer.runFunctions() logs provisioning failures 1`] = ` +[ + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){\\n insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id}\\n }","variables":{"function_name":"morgs.near/test","block_height":82699904,"message":"Running function morgs.near/test, lag is: NaNms from block timestamp"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n mutation SetStatus($function_name: String, $status: String) {\\n insert_indexer_state_one(object: {function_name: $function_name, status: $status, current_block_height: 0 }, on_conflict: { constraint: indexer_state_pkey, update_columns: status }) {\\n function_name\\n status\\n }\\n }\\n ","variables":{"function_name":"morgs.near/test","status":"PROVISIONING"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){\\n insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id}\\n }","variables":{"function_name":"morgs.near/test","block_height":82699904,"message":"Provisioning endpoint: starting"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){\\n insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id}\\n }","variables":{"function_name":"morgs.near/test","block_height":82699904,"message":"Provisioning endpoint: failure:something went wrong with provisioning"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n mutation SetStatus($function_name: String, $status: String) {\\n insert_indexer_state_one(object: {function_name: $function_name, status: $status, current_block_height: 0 }, on_conflict: { constraint: indexer_state_pkey, update_columns: status }) {\\n function_name\\n status\\n }\\n }\\n ","variables":{"function_name":"morgs.near/test","status":"STOPPED"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], +] +`; + +exports[`Indexer unit tests Indexer.runFunctions() should execute all functions against the current block 1`] = ` +[ + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){\\n insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id}\\n }","variables":{"function_name":"buildnear.testnet/test","block_height":456,"message":"Running function buildnear.testnet/test, lag is: NaNms from block timestamp"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n mutation SetStatus($function_name: String, $status: String) {\\n insert_indexer_state_one(object: {function_name: $function_name, status: $status, current_block_height: 0 }, on_conflict: { constraint: indexer_state_pkey, update_columns: status }) {\\n function_name\\n status\\n }\\n }\\n ","variables":{"function_name":"buildnear.testnet/test","status":"RUNNING"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation { set(functionName: \\"buildnear.testnet/test\\", key: \\"height\\", data: \\"456\\")}"}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "buildnear_testnet", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation WriteBlock($function_name: String!, $block_height: numeric!) {\\n insert_indexer_state(\\n objects: {current_block_height: $block_height, function_name: $function_name}\\n on_conflict: {constraint: indexer_state_pkey, update_columns: current_block_height}\\n ) {\\n returning {\\n current_block_height\\n function_name\\n }\\n }\\n }","variables":{"function_name":"buildnear.testnet/test","block_height":456}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], +] +`; + +exports[`Indexer unit tests Indexer.runFunctions() supplies the required role to the GraphQL endpoint 1`] = ` +[ + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){\\n insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id}\\n }","variables":{"function_name":"morgs.near/test","block_height":82699904,"message":"Running function morgs.near/test, lag is: NaNms from block timestamp"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"\\n mutation SetStatus($function_name: String, $status: String) {\\n insert_indexer_state_one(object: {function_name: $function_name, status: $status, current_block_height: 0 }, on_conflict: { constraint: indexer_state_pkey, update_columns: status }) {\\n function_name\\n status\\n }\\n }\\n ","variables":{"function_name":"morgs.near/test","status":"RUNNING"}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation { set(functionName: \\"buildnear.testnet/test\\", key: \\"height\\", data: \\"82699904\\")}"}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "morgs_near", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], + [ + "mock-hasura-endpoint/v1/graphql", + { + "body": "{"query":"mutation WriteBlock($function_name: String!, $block_height: numeric!) {\\n insert_indexer_state(\\n objects: {current_block_height: $block_height, function_name: $function_name}\\n on_conflict: {constraint: indexer_state_pkey, update_columns: current_block_height}\\n ) {\\n returning {\\n current_block_height\\n function_name\\n }\\n }\\n }","variables":{"function_name":"morgs.near/test","block_height":82699904}}", + "headers": { + "Content-Type": "application/json", + "X-Hasura-Admin-Secret": "mock-hasura-secret", + "X-Hasura-Role": "append", + "X-Hasura-Use-Backend-Only-Permissions": "true", + }, + "method": "POST", + }, + ], +] +`; diff --git a/runner/src/indexer/index.ts b/runner/src/indexer/index.ts new file mode 100644 index 000000000..2531410f8 --- /dev/null +++ b/runner/src/indexer/index.ts @@ -0,0 +1 @@ +export { default } from './indexer'; diff --git a/runner/src/indexer/indexer.test.ts b/runner/src/indexer/indexer.test.ts new file mode 100644 index 000000000..37a828639 --- /dev/null +++ b/runner/src/indexer/indexer.test.ts @@ -0,0 +1,1182 @@ +import { Block } from '@near-lake/primitives'; +import type fetch from 'node-fetch'; +import type AWS from 'aws-sdk'; + +import Indexer from './indexer'; +import { VM } from 'vm2'; + +describe('Indexer unit tests', () => { + const oldEnv = process.env; + + const HASURA_ENDPOINT = 'mock-hasura-endpoint'; + const HASURA_ADMIN_SECRET = 'mock-hasura-secret'; + const HASURA_ROLE = 'morgs_near'; + const INVALID_HASURA_ROLE = 'other_near'; + + const INDEXER_NAME = 'morgs.near/test_fn'; + + const SIMPLE_SCHEMA = `CREATE TABLE + "posts" ( + "id" SERIAL NOT NULL, + "account_id" VARCHAR NOT NULL, + "block_height" DECIMAL(58, 0) NOT NULL, + "receipt_id" VARCHAR NOT NULL, + "content" TEXT NOT NULL, + "block_timestamp" DECIMAL(20, 0) NOT NULL, + "accounts_liked" JSONB NOT NULL DEFAULT '[]', + "last_comment_timestamp" DECIMAL(20, 0), + CONSTRAINT "posts_pkey" PRIMARY KEY ("id") + );`; + + const SOCIAL_SCHEMA = ` + CREATE TABLE + "posts" ( + "id" SERIAL NOT NULL, + "account_id" VARCHAR NOT NULL, + "block_height" DECIMAL(58, 0) NOT NULL, + "receipt_id" VARCHAR NOT NULL, + "content" TEXT NOT NULL, + "block_timestamp" DECIMAL(20, 0) NOT NULL, + "accounts_liked" JSONB NOT NULL DEFAULT '[]', + "last_comment_timestamp" DECIMAL(20, 0), + CONSTRAINT "posts_pkey" PRIMARY KEY ("id") + ); + + CREATE TABLE + "comments" ( + "id" SERIAL NOT NULL, + "post_id" SERIAL NOT NULL, + "account_id" VARCHAR NOT NULL, + "block_height" DECIMAL(58, 0) NOT NULL, + "content" TEXT NOT NULL, + "block_timestamp" DECIMAL(20, 0) NOT NULL, + "receipt_id" VARCHAR NOT NULL, + CONSTRAINT "comments_pkey" PRIMARY KEY ("id") + ); + + CREATE TABLE + "post_likes" ( + "post_id" SERIAL NOT NULL, + "account_id" VARCHAR NOT NULL, + "block_height" DECIMAL(58, 0), + "block_timestamp" DECIMAL(20, 0) NOT NULL, + "receipt_id" VARCHAR NOT NULL, + CONSTRAINT "post_likes_pkey" PRIMARY KEY ("post_id", "account_id") + );`; + + const STRESS_TEST_SCHEMA = ` +CREATE TABLE creator_quest ( + account_id VARCHAR PRIMARY KEY, + num_components_created INTEGER NOT NULL DEFAULT 0, + completed BOOLEAN NOT NULL DEFAULT FALSE + ); + +CREATE TABLE + composer_quest ( + account_id VARCHAR PRIMARY KEY, + num_widgets_composed INTEGER NOT NULL DEFAULT 0, + completed BOOLEAN NOT NULL DEFAULT FALSE + ); + +CREATE TABLE + "contractor - quest" ( + account_id VARCHAR PRIMARY KEY, + num_contracts_deployed INTEGER NOT NULL DEFAULT 0, + completed BOOLEAN NOT NULL DEFAULT FALSE + ); + +CREATE TABLE + "posts" ( + "id" SERIAL NOT NULL, + "account_id" VARCHAR NOT NULL, + "block_height" DECIMAL(58, 0) NOT NULL, + "receipt_id" VARCHAR NOT NULL, + "content" TEXT NOT NULL, + "block_timestamp" DECIMAL(20, 0) NOT NULL, + "accounts_liked" JSONB NOT NULL DEFAULT '[]', + "last_comment_timestamp" DECIMAL(20, 0), + CONSTRAINT "posts_pkey" PRIMARY KEY ("id") + ); + +CREATE TABLE + "comments" ( + "id" SERIAL NOT NULL, + "post_id" SERIAL NOT NULL, + "account_id" VARCHAR NOT NULL, + "block_height" DECIMAL(58, 0) NOT NULL, + "content" TEXT NOT NULL, + "block_timestamp" DECIMAL(20, 0) NOT NULL, + "receipt_id" VARCHAR NOT NULL, + CONSTRAINT "comments_pkey" PRIMARY KEY ("id") + ); + +CREATE TABLE + "post_likes" ( + "post_id" SERIAL NOT NULL, + "account_id" VARCHAR NOT NULL, + "block_height" DECIMAL(58, 0), + "block_timestamp" DECIMAL(20, 0) NOT NULL, + "receipt_id" VARCHAR NOT NULL, + CONSTRAINT "post_likes_pkey" PRIMARY KEY ("post_id", "account_id") + ); + +CREATE UNIQUE INDEX "posts_account_id_block_height_key" ON "posts" ("account_id" ASC, "block_height" ASC); + +CREATE UNIQUE INDEX "comments_post_id_account_id_block_height_key" ON "comments" ( + "post_id" ASC, + "account_id" ASC, + "block_height" ASC +); + +CREATE INDEX + "posts_last_comment_timestamp_idx" ON "posts" ("last_comment_timestamp" DESC); + +ALTER TABLE + "comments" +ADD + CONSTRAINT "comments_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION; + +ALTER TABLE + "post_likes" +ADD + CONSTRAINT "post_likes_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "posts" ("id") ON DELETE CASCADE ON UPDATE NO ACTION; + +CREATE TABLE IF NOT EXISTS + "My Table1" (id serial PRIMARY KEY); + +CREATE TABLE + "Another-Table" (id serial PRIMARY KEY); + +CREATE TABLE +IF NOT EXISTS + "Third-Table" (id serial PRIMARY KEY); + +CREATE TABLE + yet_another_table (id serial PRIMARY KEY); +`; + const genericMockFetch = jest.fn() + .mockResolvedValue({ + status: 200, + json: async () => ({ + data: 'mock', + }), + }); + + beforeEach(() => { + process.env = { + ...oldEnv, + HASURA_ENDPOINT, + HASURA_ADMIN_SECRET + }; + }); + + afterAll(() => { + process.env = oldEnv; + }); + + test('Indexer.runFunctions() should execute all functions against the current block', async () => { + const mockFetch = jest.fn(() => ({ + status: 200, + json: async () => ({ + errors: null, + }), + })); + const blockHeight = 456; + const mockS3 = { + getObject: jest.fn(() => ({ + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + chunks: [], + header: { + height: blockHeight + } + }) + } + }) + })), + } as unknown as AWS.S3; + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch, s3: mockS3 }); + + const functions: Record = {}; + functions['buildnear.testnet/test'] = { + code: ` + const foo = 3; + block.result = context.graphql(\`mutation { set(functionName: "buildnear.testnet/test", key: "height", data: "\${block.blockHeight}")}\`); + `, + schema: SIMPLE_SCHEMA + }; + await indexer.runFunctions(blockHeight, functions, false); + + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + test('Indexer.fetchBlock() should fetch a block from the S3', async () => { + const author = 'dokiacapital.poolv1.near'; + const mockS3 = { + getObject: jest.fn(() => ({ + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + author + }) + } + }) + })), + } as unknown as AWS.S3; + const indexer = new Indexer('mainnet', { s3: mockS3 }); + + const blockHeight = 84333960; + const block = await indexer.fetchBlockPromise(blockHeight); + + expect(mockS3.getObject).toHaveBeenCalledTimes(1); + expect(mockS3.getObject).toHaveBeenCalledWith({ + Bucket: 'near-lake-data-mainnet', + Key: `${blockHeight.toString().padStart(12, '0')}/block.json` + }); + expect(block.author).toEqual(author); + }); + + test('Indexer.fetchShard() should fetch the steamer message from S3', async () => { + const mockS3 = { + getObject: jest.fn(() => ({ + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({}) + } + }) + })), + } as unknown as AWS.S3; + const indexer = new Indexer('mainnet', { s3: mockS3 }); + + const blockHeight = 82699904; + const shard = 0; + await indexer.fetchShardPromise(blockHeight, shard); + + expect(mockS3.getObject).toHaveBeenCalledTimes(1); + expect(mockS3.getObject).toHaveBeenCalledWith({ + Bucket: 'near-lake-data-mainnet', + Key: `${blockHeight.toString().padStart(12, '0')}/shard_${shard}.json` + }); + }); + + test('Indexer.fetchStreamerMessage() should fetch the block/shards and construct the streamer message', async () => { + const blockHeight = 85233529; + const blockHash = 'xyz'; + const getObject = jest.fn() + .mockReturnValueOnce({ // block + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + chunks: [0], + header: { + height: blockHeight, + hash: blockHash, + } + }) + } + }) + }) + .mockReturnValue({ // shard + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({}) + } + }) + }); + const mockS3 = { + getObject, + } as unknown as AWS.S3; + const indexer = new Indexer('mainnet', { s3: mockS3 }); + + const streamerMessage = await indexer.fetchStreamerMessage(blockHeight); + + expect(getObject).toHaveBeenCalledTimes(5); + expect(getObject.mock.calls[0][0]).toEqual({ + Bucket: 'near-lake-data-mainnet', + Key: `${blockHeight.toString().padStart(12, '0')}/block.json` + }); + expect(getObject.mock.calls[1][0]).toEqual({ + Bucket: 'near-lake-data-mainnet', + Key: `${blockHeight.toString().padStart(12, '0')}/shard_0.json` + }); + + const block = Block.fromStreamerMessage(streamerMessage); + + expect(block.blockHeight).toEqual(blockHeight); + expect(block.blockHash).toEqual(blockHash); + }); + + test('Indexer.transformIndexerFunction() applies the necessary transformations', () => { + const indexer = new Indexer('mainnet'); + + const transformedFunction = indexer.transformIndexerFunction('console.log(\'hello\')'); + + expect(transformedFunction).toEqual(` + async function f(){ + console.log('hello') + }; + f(); + `); + }); + + test('Indexer.buildContext() allows execution of arbitrary GraphQL operations', async () => { + const mockFetch = jest.fn() + .mockResolvedValueOnce({ + status: 200, + json: async () => ({ + data: { + greet: 'hello' + } + }) + }) + .mockResolvedValueOnce({ + status: 200, + json: async () => ({ + data: { + newGreeting: { + success: true + } + } + }) + }); + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch }); + + const context = indexer.buildContext(SIMPLE_SCHEMA, INDEXER_NAME, 1, HASURA_ROLE); + + const query = ` + query { + greet() + } + `; + const { greet } = await context.graphql(query) as { greet: string }; + + const mutation = ` + mutation { + newGreeting(greeting: "${greet} morgan") { + success + } + } + `; + const { newGreeting: { success } } = await context.graphql(mutation); + + expect(greet).toEqual('hello'); + expect(success).toEqual(true); + expect(mockFetch.mock.calls[0]).toEqual([ + `${HASURA_ENDPOINT}/v1/graphql`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Hasura-Use-Backend-Only-Permissions': 'true', + 'X-Hasura-Role': 'morgs_near', + 'X-Hasura-Admin-Secret': HASURA_ADMIN_SECRET + }, + body: JSON.stringify({ query }) + } + ]); + expect(mockFetch.mock.calls[1]).toEqual([ + `${HASURA_ENDPOINT}/v1/graphql`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Hasura-Use-Backend-Only-Permissions': 'true', + 'X-Hasura-Role': 'morgs_near', + 'X-Hasura-Admin-Secret': HASURA_ADMIN_SECRET + }, + body: JSON.stringify({ query: mutation }) + } + ]); + }); + + test('Indexer.buildContext() can fetch from the near social api', async () => { + const mockFetch = jest.fn(); + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch }); + + const context = indexer.buildContext(SIMPLE_SCHEMA, INDEXER_NAME, 1, HASURA_ROLE); + + await context.fetchFromSocialApi('/index', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + action: 'post', + key: 'main', + options: { + limit: 1, + order: 'desc' + } + }) + }); + + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + test('Indexer.buildContext() throws when a GraphQL response contains errors', async () => { + const mockFetch = jest.fn() + .mockResolvedValue({ + json: async () => ({ + errors: ['boom'] + }) + }); + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch }); + + const context = indexer.buildContext(SIMPLE_SCHEMA, INDEXER_NAME, 1, INVALID_HASURA_ROLE); + + await expect(async () => await context.graphql('query { hello }')).rejects.toThrow('boom'); + }); + + test('Indexer.buildContext() handles GraphQL variables', async () => { + const mockFetch = jest.fn() + .mockResolvedValue({ + status: 200, + json: async () => ({ + data: 'mock', + }), + }); + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch }); + + const context = indexer.buildContext(SIMPLE_SCHEMA, INDEXER_NAME, 1, HASURA_ROLE); + + const query = 'query($name: String) { hello(name: $name) }'; + const variables = { name: 'morgan' }; + await context.graphql(query, variables); + + expect(mockFetch.mock.calls[0]).toEqual([ + `${HASURA_ENDPOINT}/v1/graphql`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Hasura-Use-Backend-Only-Permissions': 'true', + 'X-Hasura-Role': 'morgs_near', + 'X-Hasura-Admin-Secret': HASURA_ADMIN_SECRET + }, + body: JSON.stringify({ + query, + variables, + }), + }, + ]); + }); + + test('GetTables works for a variety of input schemas', async () => { + const indexer = new Indexer('mainnet'); + + const simpleSchemaTables = indexer.getTableNames(SIMPLE_SCHEMA); + expect(simpleSchemaTables).toStrictEqual(['posts']); + + const socialSchemaTables = indexer.getTableNames(SOCIAL_SCHEMA); + expect(socialSchemaTables).toStrictEqual(['posts', 'comments', 'post_likes']); + + const stressTestSchemaTables = indexer.getTableNames(STRESS_TEST_SCHEMA); + expect(stressTestSchemaTables).toStrictEqual([ + 'creator_quest', + 'composer_quest', + 'contractor - quest', + 'posts', + 'comments', + 'post_likes', + 'My Table1', + 'Another-Table', + 'Third-Table', + 'yet_another_table']); + + // Test that duplicate table names throw an error + const duplicateTableSchema = `CREATE TABLE + "posts" ( + "id" SERIAL NOT NULL + ); + CREATE TABLE posts ( + "id" SERIAL NOT NULL + );`; + expect(() => { + indexer.getTableNames(duplicateTableSchema); + }).toThrow('Table posts already exists in schema. Table names must be unique. Quotes are not allowed as a differentiator between table names.'); + + // Test that schema with no tables throws an error + expect(() => { + indexer.getTableNames(''); + }).toThrow('Schema does not have any tables. There should be at least one table.'); + }); + + test('SanitizeTableName works properly on many test cases', async () => { + const indexer = new Indexer('mainnet'); + + expect(indexer.sanitizeTableName('table_name')).toStrictEqual('TableName'); + expect(indexer.sanitizeTableName('tablename')).toStrictEqual('Tablename'); // name is not capitalized + expect(indexer.sanitizeTableName('table name')).toStrictEqual('TableName'); + expect(indexer.sanitizeTableName('table!name!')).toStrictEqual('TableName'); + expect(indexer.sanitizeTableName('123TABle')).toStrictEqual('_123TABle'); // underscore at beginning + expect(indexer.sanitizeTableName('123_tABLE')).toStrictEqual('_123TABLE'); // underscore at beginning, capitalization + expect(indexer.sanitizeTableName('some-table_name')).toStrictEqual('SomeTableName'); + expect(indexer.sanitizeTableName('!@#$%^&*()table@)*&(%#')).toStrictEqual('Table'); // All special characters removed + expect(indexer.sanitizeTableName('T_name')).toStrictEqual('TName'); + expect(indexer.sanitizeTableName('_table')).toStrictEqual('Table'); // Starting underscore was removed + }); + + test('indexer fails to build context.db due to collision on sanitized table names', async () => { + const indexer = new Indexer('mainnet'); + + const schemaWithDuplicateSanitizedTableNames = `CREATE TABLE + "test table" ( + "id" SERIAL NOT NULL + ); + CREATE TABLE "test!table" ( + "id" SERIAL NOT NULL + );`; + + // Does not outright throw an error but instead returns an empty object + expect(indexer.buildDatabaseContext('test_account', 'test_schema_name', schemaWithDuplicateSanitizedTableNames, 1)) + .toStrictEqual({}); + }); + + test('indexer builds context and inserts an objects into existing table', async () => { + const mockDmlHandler: any = { + create: jest.fn().mockImplementation(() => { + return { insert: jest.fn().mockReturnValue([{ colA: 'valA' }, { colA: 'valA' }]) }; + }) + }; + + const indexer = new Indexer('mainnet', { fetch: genericMockFetch as unknown as typeof fetch, DmlHandler: mockDmlHandler }); + const context = indexer.buildContext(SOCIAL_SCHEMA, 'morgs.near/social_feed1', 1, 'postgres'); + + const objToInsert = [{ + account_id: 'morgs_near', + block_height: 1, + receipt_id: 'abc', + content: 'test', + block_timestamp: 800, + accounts_liked: JSON.stringify(['cwpuzzles.near', 'devbose.near']) + }, + { + account_id: 'morgs_near', + block_height: 2, + receipt_id: 'abc', + content: 'test', + block_timestamp: 801, + accounts_liked: JSON.stringify(['cwpuzzles.near']) + }]; + + const result = await context.db.Posts.insert(objToInsert); + expect(result.length).toEqual(2); + }); + + test('indexer builds context and selects objects from existing table', async () => { + const selectFn = jest.fn(); + selectFn.mockImplementation((...args) => { + // Expects limit to be last parameter + return args[args.length - 1] === null ? [{ colA: 'valA' }, { colA: 'valA' }] : [{ colA: 'valA' }]; + }); + const mockDmlHandler: any = { + create: jest.fn().mockImplementation(() => { + return { select: selectFn }; + }) + }; + + const indexer = new Indexer('mainnet', { fetch: genericMockFetch as unknown as typeof fetch, DmlHandler: mockDmlHandler }); + const context = indexer.buildContext(SOCIAL_SCHEMA, 'morgs.near/social_feed1', 1, 'postgres'); + + const objToSelect = { + account_id: 'morgs_near', + receipt_id: 'abc', + }; + const result = await context.db.Posts.select(objToSelect); + expect(result.length).toEqual(2); + const resultLimit = await context.db.Posts.select(objToSelect, 1); + expect(resultLimit.length).toEqual(1); + }); + + test('indexer builds context and updates multiple objects from existing table', async () => { + const mockDmlHandler: any = { + create: jest.fn().mockImplementation(() => { + return { + update: jest.fn().mockImplementation((_, __, whereObj, updateObj) => { + if (whereObj.account_id === 'morgs_near' && updateObj.content === 'test_content') { + return [{ colA: 'valA' }, { colA: 'valA' }]; + } + return [{}]; + }) + }; + }) + }; + + const indexer = new Indexer('mainnet', { fetch: genericMockFetch as unknown as typeof fetch, DmlHandler: mockDmlHandler }); + const context = indexer.buildContext(SOCIAL_SCHEMA, 'morgs.near/social_feed1', 1, 'postgres'); + + const whereObj = { + account_id: 'morgs_near', + receipt_id: 'abc', + }; + const updateObj = { + content: 'test_content', + block_timestamp: 805, + }; + const result = await context.db.Posts.update(whereObj, updateObj); + expect(result.length).toEqual(2); + }); + + test('indexer builds context and upserts on existing table', async () => { + const mockDmlHandler: any = { + create: jest.fn().mockImplementation(() => { + return { + upsert: jest.fn().mockImplementation((_, __, objects, conflict, update) => { + if (objects.length === 2 && conflict.includes('account_id') && update.includes('content')) { + return [{ colA: 'valA' }, { colA: 'valA' }]; + } else if (objects.length === 1 && conflict.includes('account_id') && update.includes('content')) { + return [{ colA: 'valA' }]; + } + return [{}]; + }) + }; + }) + }; + + const indexer = new Indexer('mainnet', { fetch: genericMockFetch as unknown as typeof fetch, DmlHandler: mockDmlHandler }); + const context = indexer.buildContext(SOCIAL_SCHEMA, 'morgs.near/social_feed1', 1, 'postgres'); + + const objToInsert = [{ + account_id: 'morgs_near', + block_height: 1, + receipt_id: 'abc', + content: 'test', + block_timestamp: 800, + accounts_liked: JSON.stringify(['cwpuzzles.near', 'devbose.near']) + }, + { + account_id: 'morgs_near', + block_height: 2, + receipt_id: 'abc', + content: 'test', + block_timestamp: 801, + accounts_liked: JSON.stringify(['cwpuzzles.near']) + }]; + + let result = await context.db.Posts.upsert(objToInsert, ['account_id', 'block_height'], ['content', 'block_timestamp']); + expect(result.length).toEqual(2); + result = await context.db.Posts.upsert(objToInsert[0], ['account_id', 'block_height'], ['content', 'block_timestamp']); + expect(result.length).toEqual(1); + }); + + test('indexer builds context and deletes objects from existing table', async () => { + const mockDmlHandler: any = { + create: jest.fn().mockImplementation(() => { + return { delete: jest.fn().mockReturnValue([{ colA: 'valA' }, { colA: 'valA' }]) }; + }) + }; + + const indexer = new Indexer('mainnet', { fetch: genericMockFetch as unknown as typeof fetch, DmlHandler: mockDmlHandler }); + const context = indexer.buildContext(SOCIAL_SCHEMA, 'morgs.near/social_feed1', 1, 'postgres'); + + const deleteFilter = { + account_id: 'morgs_near', + receipt_id: 'abc', + }; + const result = await context.db.Posts.delete(deleteFilter); + expect(result.length).toEqual(2); + }); + + test('indexer builds context and verifies all methods generated', async () => { + const mockDmlHandler: any = { + create: jest.fn() + }; + + const indexer = new Indexer('mainnet', { fetch: genericMockFetch as unknown as typeof fetch, DmlHandler: mockDmlHandler }); + const context = indexer.buildContext(STRESS_TEST_SCHEMA, 'morgs.near/social_feed1', 1, 'postgres'); + + expect(Object.keys(context.db)).toStrictEqual([ + 'CreatorQuest', + 'ComposerQuest', + 'ContractorQuest', + 'Posts', + 'Comments', + 'PostLikes', + 'MyTable1', + 'AnotherTable', + 'ThirdTable', + 'YetAnotherTable']); + expect(Object.keys(context.db.CreatorQuest)).toStrictEqual([ + 'insert', + 'select', + 'update', + 'upsert', + 'delete']); + expect(Object.keys(context.db.PostLikes)).toStrictEqual([ + 'insert', + 'select', + 'update', + 'upsert', + 'delete']); + expect(Object.keys(context.db.MyTable1)).toStrictEqual([ + 'insert', + 'select', + 'update', + 'upsert', + 'delete']); + }); + + test('indexer builds context and returns empty array if failed to generate db methods', async () => { + const mockDmlHandler: any = { + create: jest.fn() + }; + + const indexer = new Indexer('mainnet', { fetch: genericMockFetch as unknown as typeof fetch, DmlHandler: mockDmlHandler }); + const context = indexer.buildContext('', 'morgs.near/social_feed1', 1, 'postgres'); + + expect(Object.keys(context.db)).toStrictEqual([]); + }); + + test('Indexer.runFunctions() allows imperative execution of GraphQL operations', async () => { + const postId = 1; + const commentId = 2; + const blockHeight = 82699904; + const mockFetch = jest.fn() + .mockReturnValueOnce({ // starting log + status: 200, + json: async () => ({ + data: { + indexer_log_store: [ + { + id: '12345', + }, + ], + }, + }), + }) + .mockReturnValueOnce({ + status: 200, + json: async () => ({ + errors: null, + }), + }) + .mockReturnValueOnce({ // query + status: 200, + json: async () => ({ + data: { + posts: [ + { + id: postId, + }, + ], + }, + }), + }) + .mockReturnValueOnce({ // mutation + status: 200, + json: async () => ({ + data: { + insert_comments: { + returning: { + id: commentId, + }, + }, + }, + }), + }) + .mockReturnValueOnce({ + status: 200, + json: async () => ({ + errors: null, + }), + }); + + const mockS3 = { + getObject: jest.fn() + .mockReturnValueOnce({ // block + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + chunks: [0], + header: { + height: blockHeight, + }, + }), + }, + }), + }) + .mockReturnValue({ // shard + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({}) + }, + }), + }), + } as unknown as AWS.S3; + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch, s3: mockS3 }); + + const functions: Record = {}; + functions['buildnear.testnet/test'] = { + code: ` + const { posts } = await context.graphql(\` + query { + posts(where: { id: { _eq: 1 } }) { + id + } + } + \`); + + if (!posts || posts.length === 0) { + return; + } + + const [post] = posts; + + const { insert_comments: { returning: { id } } } = await context.graphql(\` + mutation { + insert_comments( + objects: {account_id: "morgs.near", block_height: \${block.blockHeight}, content: "cool post", post_id: \${post.id}} + ) { + returning { + id + } + } + } + \`); + + return (\`Created comment \${id} on post \${post.id}\`) + `, + schema: SIMPLE_SCHEMA + }; + + await indexer.runFunctions(blockHeight, functions, false); + + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + test('Indexer.runFunctions() console.logs', async () => { + const logs: string[] = []; + const context = { + log: (...m: string[]) => { + logs.push(...m); + } + }; + const vm = new VM(); + vm.freeze(context, 'context'); + vm.freeze(context, 'console'); + await vm.run('console.log("hello", "brave new"); context.log("world")'); + expect(logs).toEqual(['hello', 'brave new', 'world']); + }); + + test('Errors thrown in VM can be caught outside the VM', async () => { + const vm = new VM(); + expect(() => { + vm.run("throw new Error('boom')"); + }).toThrow('boom'); + }); + + test('Indexer.runFunctions() catches errors', async () => { + const mockFetch = jest.fn(() => ({ + status: 200, + json: async () => ({ + errors: null, + }), + })); + const blockHeight = 456; + const mockS3 = { + getObject: jest.fn(() => ({ + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + chunks: [], + header: { + height: blockHeight + } + }) + } + }) + })), + } as unknown as AWS.S3; + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch, s3: mockS3 }); + + const functions: Record = {}; + functions['buildnear.testnet/test'] = { + code: ` + throw new Error('boom'); + `, + schema: SIMPLE_SCHEMA + }; + + await expect(indexer.runFunctions(blockHeight, functions, false)).rejects.toThrow(new Error('boom')); + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + test('Indexer.runFunctions() provisions a GraphQL endpoint with the specified schema', async () => { + const blockHeight = 82699904; + const mockFetch = jest.fn(() => ({ + status: 200, + json: async () => ({ + errors: null, + }), + })); + const mockS3 = { + getObject: jest + .fn() + .mockReturnValueOnce({ // block + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + chunks: [0], + header: { + height: blockHeight, + }, + }), + }, + }), + }) + .mockReturnValue({ // shard + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({}) + }, + }), + }), + } as unknown as AWS.S3; + const provisioner: any = { + isUserApiProvisioned: jest.fn().mockReturnValue(false), + provisionUserApi: jest.fn(), + }; + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch, s3: mockS3, provisioner }); + + const functions = { + 'morgs.near/test': { + account_id: 'morgs.near', + function_name: 'test', + code: '', + schema: SIMPLE_SCHEMA, + } + }; + await indexer.runFunctions(1, functions, false, { provision: true }); + + expect(provisioner.isUserApiProvisioned).toHaveBeenCalledWith('morgs.near', 'test'); + expect(provisioner.provisionUserApi).toHaveBeenCalledTimes(1); + expect(provisioner.provisionUserApi).toHaveBeenCalledWith( + 'morgs.near', + 'test', + SIMPLE_SCHEMA + ); + }); + + test('Indexer.runFunctions() skips provisioning if the endpoint exists', async () => { + const blockHeight = 82699904; + const mockFetch = jest.fn(() => ({ + status: 200, + json: async () => ({ + errors: null, + }), + })); + const mockS3 = { + getObject: jest + .fn() + .mockReturnValueOnce({ // block + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + chunks: [0], + header: { + height: blockHeight, + }, + }), + }, + }), + }) + .mockReturnValue({ // shard + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({}) + }, + }), + }), + } as unknown as AWS.S3; + const provisioner: any = { + isUserApiProvisioned: jest.fn().mockReturnValue(true), + provisionUserApi: jest.fn(), + }; + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch, s3: mockS3, provisioner }); + + const functions: Record = { + 'morgs.near/test': { + code: '', + schema: SIMPLE_SCHEMA, + } + }; + await indexer.runFunctions(1, functions, false, { provision: true }); + + expect(provisioner.provisionUserApi).not.toHaveBeenCalled(); + }); + + test('Indexer.runFunctions() supplies the required role to the GraphQL endpoint', async () => { + const blockHeight = 82699904; + const mockFetch = jest.fn(() => ({ + status: 200, + json: async () => ({ + errors: null, + }), + })); + const mockS3 = { + getObject: jest + .fn() + .mockReturnValueOnce({ // block + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + chunks: [0], + header: { + height: blockHeight, + }, + }), + }, + }), + }) + .mockReturnValue({ // shard + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({}) + }, + }), + }), + } as unknown as AWS.S3; + const provisioner: any = { + isUserApiProvisioned: jest.fn().mockReturnValue(true), + provisionUserApi: jest.fn(), + }; + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch, s3: mockS3, provisioner }); + + const functions: Record = { + 'morgs.near/test': { + code: ` + context.graphql(\`mutation { set(functionName: "buildnear.testnet/test", key: "height", data: "\${block.blockHeight}")}\`); + `, + schema: SIMPLE_SCHEMA, + } + }; + await indexer.runFunctions(blockHeight, functions, false, { provision: true }); + + expect(provisioner.provisionUserApi).not.toHaveBeenCalled(); + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + test('Indexer.runFunctions() logs provisioning failures', async () => { + const blockHeight = 82699904; + const mockFetch = jest.fn(() => ({ + status: 200, + json: async () => ({ + errors: null, + }), + })); + const mockS3 = { + getObject: jest + .fn() + .mockReturnValueOnce({ // block + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({ + chunks: [0], + header: { + height: blockHeight, + }, + }), + }, + }), + }) + .mockReturnValue({ // shard + promise: async () => await Promise.resolve({ + Body: { + toString: () => JSON.stringify({}) + }, + }), + }), + } as unknown as AWS.S3; + const error = new Error('something went wrong with provisioning'); + const provisioner: any = { + isUserApiProvisioned: jest.fn().mockReturnValue(false), + provisionUserApi: jest.fn().mockRejectedValue(error), + }; + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch, s3: mockS3, provisioner }); + + const functions: Record = { + 'morgs.near/test': { + code: ` + context.graphql(\`mutation { set(functionName: "buildnear.testnet/test", key: "height", data: "\${block.blockHeight}")}\`); + `, + schema: 'schema', + } + }; + + await expect(indexer.runFunctions(blockHeight, functions, false, { provision: true })).rejects.toThrow(error); + expect(mockFetch.mock.calls).toMatchSnapshot(); + }); + + test('does not attach the hasura admin secret header when no role specified', async () => { + const mockFetch = jest.fn() + .mockResolvedValueOnce({ + status: 200, + json: async () => ({ + data: {} + }) + }); + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch }); + // @ts-expect-error legacy test + const context = indexer.buildContext(SIMPLE_SCHEMA, INDEXER_NAME, 1, null); + + const mutation = ` + mutation { + newGreeting(greeting: "howdy") { + success + } + } + `; + + await context.graphql(mutation); + + expect(mockFetch.mock.calls[0]).toEqual([ + `${HASURA_ENDPOINT}/v1/graphql`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Hasura-Use-Backend-Only-Permissions': 'true', + }, + body: JSON.stringify({ query: mutation }) + } + ]); + }); + + test('attaches the backend only header to requests to hasura', async () => { + const mockFetch = jest.fn() + .mockResolvedValueOnce({ + status: 200, + json: async () => ({ + data: {} + }) + }); + const role = 'morgs_near'; + const indexer = new Indexer('mainnet', { fetch: mockFetch as unknown as typeof fetch }); + const context = indexer.buildContext(SIMPLE_SCHEMA, INDEXER_NAME, 1, HASURA_ROLE); + + const mutation = ` + mutation { + newGreeting(greeting: "howdy") { + success + } + } + `; + + await context.graphql(mutation); + + expect(mockFetch.mock.calls[0]).toEqual([ + `${HASURA_ENDPOINT}/v1/graphql`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Hasura-Use-Backend-Only-Permissions': 'true', + 'X-Hasura-Role': role, + 'X-Hasura-Admin-Secret': HASURA_ADMIN_SECRET + }, + body: JSON.stringify({ query: mutation }) + } + ]); + }); +}); diff --git a/runner/src/indexer/indexer.ts b/runner/src/indexer/indexer.ts new file mode 100644 index 000000000..3a2858cd5 --- /dev/null +++ b/runner/src/indexer/indexer.ts @@ -0,0 +1,501 @@ +import fetch, { type Response } from 'node-fetch'; +import { VM } from 'vm2'; +import AWS from 'aws-sdk'; +import { Block } from '@near-lake/primitives'; +import { Parser } from 'node-sql-parser'; + +import Provisioner from '../provisioner'; +import DmlHandler from '../dml-handler/dml-handler'; + +interface Dependencies { + fetch: typeof fetch + s3: AWS.S3 + provisioner: Provisioner + DmlHandler: typeof DmlHandler + parser: Parser +}; + +interface Context { + graphql: (operation: string, variables?: Record) => Promise + set: (key: string, value: any) => Promise + log: (...log: any[]) => Promise + fetchFromSocialApi: (path: string, options?: any) => Promise + db: Record any>> +} + +interface IndexerFunction { + account_id: string + function_name: string + provisioned?: boolean + schema: string + code: string +} + +export default class Indexer { + DEFAULT_HASURA_ROLE; + + private readonly deps: Dependencies; + + constructor ( + private readonly network: string, + deps?: Partial + ) { + this.DEFAULT_HASURA_ROLE = 'append'; + this.network = network; + this.deps = { + fetch, + s3: new AWS.S3(), + provisioner: new Provisioner(), + DmlHandler, + parser: new Parser(), + ...deps, + }; + } + + async runFunctions ( + blockHeight: number, + functions: Record, + isHistorical: boolean, + options: { provision?: boolean } = { provision: false } + ): Promise { + const blockWithHelpers = Block.fromStreamerMessage(await this.fetchStreamerMessage(blockHeight)); + + const lag = Date.now() - Math.floor(Number(blockWithHelpers.header().timestampNanosec) / 1000000); + + const simultaneousPromises: Array> = []; + const allMutations: string[] = []; + + for (const functionName in functions) { + try { + const indexerFunction = functions[functionName]; + + const runningMessage = `Running function ${functionName}` + (isHistorical ? ' historical backfill' : `, lag is: ${lag?.toString()}ms from block timestamp`); + console.log(runningMessage); // Print the running message to the console (Lambda logs) + + simultaneousPromises.push(this.writeLog(functionName, blockHeight, runningMessage)); + + const hasuraRoleName = functionName.split('/')[0].replace(/[.-]/g, '_'); + + if (options.provision && !indexerFunction.provisioned) { + try { + if (!await this.deps.provisioner.isUserApiProvisioned(indexerFunction.account_id, indexerFunction.function_name)) { + await this.setStatus(functionName, blockHeight, 'PROVISIONING'); + simultaneousPromises.push(this.writeLog(functionName, blockHeight, 'Provisioning endpoint: starting')); + + await this.deps.provisioner.provisionUserApi(indexerFunction.account_id, indexerFunction.function_name, indexerFunction.schema); + + simultaneousPromises.push(this.writeLog(functionName, blockHeight, 'Provisioning endpoint: successful')); + } + } catch (e) { + const error = e as Error; + simultaneousPromises.push(this.writeLog(functionName, blockHeight, 'Provisioning endpoint: failure', error.message)); + throw error; + } + } + + await this.setStatus(functionName, blockHeight, 'RUNNING'); + + const vm = new VM({ timeout: 3000, allowAsync: true }); + const context = this.buildContext(indexerFunction.schema, functionName, blockHeight, hasuraRoleName); + + vm.freeze(blockWithHelpers, 'block'); + vm.freeze(context, 'context'); + vm.freeze(context, 'console'); // provide console.log via context.log + + const modifiedFunction = this.transformIndexerFunction(indexerFunction.code); + try { + await vm.run(modifiedFunction); + } catch (e) { + const error = e as Error; + // NOTE: logging the exception would likely leak some information about the index runner. + // For now, we just log the message. In the future we could sanitize the stack trace + // and give the correct line number offsets within the indexer function + console.error(`${functionName}: Error running IndexerFunction on block ${blockHeight}: ${error.message}`); + await this.writeLog(functionName, blockHeight, 'Error running IndexerFunction', error.message); + throw e; + } + + simultaneousPromises.push(this.writeFunctionState(functionName, blockHeight, isHistorical)); + } catch (e) { + console.error(`${functionName}: Failed to run function`, e); + await this.setStatus(functionName, blockHeight, 'STOPPED'); + throw e; + } finally { + await Promise.all(simultaneousPromises); + } + } + return allMutations; + } + + // pad with 0s to 12 digits + normalizeBlockHeight (blockHeight: number): string { + return blockHeight.toString().padStart(12, '0'); + } + + async fetchStreamerMessage (blockHeight: number): Promise<{ block: any, shards: any[] }> { + const blockPromise = this.fetchBlockPromise(blockHeight); + const shardsPromises = await this.fetchShardsPromises(blockHeight, 4); + + const results = await Promise.all([blockPromise, ...shardsPromises]); + const block = results.shift(); + const shards = results; + return { + block, + shards, + }; + } + + async fetchShardsPromises (blockHeight: number, numberOfShards: number): Promise>> { + return ([...Array(numberOfShards).keys()].map(async (shardId) => + await this.fetchShardPromise(blockHeight, shardId) + )); + } + + async fetchShardPromise (blockHeight: number, shardId: number): Promise { + const params = { + Bucket: `near-lake-data-${this.network}`, + Key: `${this.normalizeBlockHeight(blockHeight)}/shard_${shardId}.json`, + }; + return await this.deps.s3.getObject(params).promise().then((response) => { + return JSON.parse(response.Body?.toString() ?? '{}', (_key, value) => this.renameUnderscoreFieldsToCamelCase(value)); + }); + } + + async fetchBlockPromise (blockHeight: number): Promise { + const file = 'block.json'; + const folder = this.normalizeBlockHeight(blockHeight); + const params = { + Bucket: 'near-lake-data-' + this.network, + Key: `${folder}/${file}`, + }; + return await this.deps.s3.getObject(params).promise().then((response) => { + const block = JSON.parse(response.Body?.toString() ?? '{}', (_key, value) => this.renameUnderscoreFieldsToCamelCase(value)); + return block; + }); + } + + enableAwaitTransform (indexerFunction: string): string { + return ` + async function f(){ + ${indexerFunction} + }; + f(); + `; + } + + transformIndexerFunction (indexerFunction: string): string { + return [ + this.enableAwaitTransform, + ].reduce((acc, val) => val(acc), indexerFunction); + } + + buildContext (schema: string, functionName: string, blockHeight: number, hasuraRoleName: string): Context { + const account = functionName.split('/')[0].replace(/[.-]/g, '_'); + const functionNameWithoutAccount = functionName.split('/')[1].replace(/[.-]/g, '_'); + const schemaName = functionName.replace(/[^a-zA-Z0-9]/g, '_'); + + return { + graphql: async (operation, variables) => { + console.log(`${functionName}: Running context graphql`, operation); + return await this.runGraphQLQuery(operation, variables, functionName, blockHeight, hasuraRoleName); + }, + set: async (key, value) => { + const mutation = + `mutation SetKeyValue($function_name: String!, $key: String!, $value: String!) { + insert_${hasuraRoleName}_${functionNameWithoutAccount}_indexer_storage_one(object: {function_name: $function_name, key_name: $key, value: $value} on_conflict: {constraint: indexer_storage_pkey, update_columns: value}) {key_name} + }`; + const variables = { + function_name: functionName, + key, + value: value ? JSON.stringify(value) : null + }; + console.log(`${functionName}: Running set:`, mutation, variables); + return await this.runGraphQLQuery(mutation, variables, functionName, blockHeight, hasuraRoleName); + }, + log: async (...log) => { + return await this.writeLog(functionName, blockHeight, ...log); + }, + fetchFromSocialApi: async (path, options) => { + return await this.deps.fetch(`https://api.near.social${path}`, options); + }, + db: this.buildDatabaseContext(account, schemaName, schema, blockHeight) + }; + } + + getTableNames (schema: string): string[] { + let schemaSyntaxTree = this.deps.parser.astify(schema, { database: 'Postgresql' }); + schemaSyntaxTree = Array.isArray(schemaSyntaxTree) ? schemaSyntaxTree : [schemaSyntaxTree]; // Ensure iterable + const tableNames = new Set(); + + // Collect all table names from schema AST, throw error if duplicate table names exist + for (const statement of schemaSyntaxTree) { + if (statement.type === 'create' && statement.keyword === 'table' && statement.table !== undefined) { + const tableName: string = statement.table[0].table; + + if (tableNames.has(tableName)) { + throw new Error(`Table ${tableName} already exists in schema. Table names must be unique. Quotes are not allowed as a differentiator between table names.`); + } + + tableNames.add(tableName); + } + } + + // Ensure schema is not empty + if (tableNames.size === 0) { + throw new Error('Schema does not have any tables. There should be at least one table.'); + } + + const tableNamesArray = Array.from(tableNames); + console.log('Retrieved the following table names from schema: ', tableNamesArray); + return Array.from(tableNamesArray); + } + + sanitizeTableName (tableName: string): string { + // Convert to PascalCase + let pascalCaseTableName = tableName + // Replace special characters with underscores + .replace(/[^a-zA-Z0-9_]/g, '_') + // Makes first letter and any letters following an underscore upper case + .replace(/^([a-zA-Z])|_([a-zA-Z])/g, (match: string) => match.toUpperCase()) + // Removes all underscores + .replace(/_/g, ''); + + // Add underscore if first character is a number + if (/^[0-9]/.test(pascalCaseTableName)) { + pascalCaseTableName = '_' + pascalCaseTableName; + } + + return pascalCaseTableName; + } + + buildDatabaseContext (account: string, schemaName: string, schema: string, blockHeight: number): Record any>> { + try { + const tables = this.getTableNames(schema); + const sanitizedTableNames = new Set(); + let dmlHandler: DmlHandler; + + // Generate and collect methods for each table name + const result = tables.reduce((prev, tableName) => { + // Generate sanitized table name and ensure no conflict + const sanitizedTableName = this.sanitizeTableName(tableName); + if (sanitizedTableNames.has(sanitizedTableName)) { + throw new Error(`Table ${tableName} has the same sanitized name as another table. Special characters are removed to generate context.db methods. Please rename the table.`); + } else { + sanitizedTableNames.add(sanitizedTableName); + } + + // Generate context.db methods for table + const defaultLog = `Calling context.db.${sanitizedTableName}.`; + const funcForTable = { + [`${sanitizedTableName}`]: { + insert: async (objectsToInsert: any) => { + // Write log before calling insert + await this.writeLog(`context.db.${sanitizedTableName}.insert`, blockHeight, defaultLog + '.insert', + `Inserting object ${JSON.stringify(objectsToInsert)} into table ${tableName} on schema ${schemaName}`); + + // Create DmlHandler if it doesn't exist + dmlHandler = dmlHandler ?? await this.deps.DmlHandler.create(account); + + // Call insert with parameters + return await dmlHandler.insert(schemaName, tableName, Array.isArray(objectsToInsert) ? objectsToInsert : [objectsToInsert]); + }, + select: async (filterObj: any, limit = null) => { + // Write log before calling select + await this.writeLog(`context.db.${sanitizedTableName}.select`, blockHeight, defaultLog + '.select', + `Selecting objects with values ${JSON.stringify(filterObj)} in table ${tableName} on schema ${schemaName} with ${limit === null ? 'no' : limit} limit`); + + // Create DmlHandler if it doesn't exist + dmlHandler = dmlHandler ?? await this.deps.DmlHandler.create(account); + + // Call select with parameters + return await dmlHandler.select(schemaName, tableName, filterObj, limit); + }, + update: async (filterObj: any, updateObj: any) => { + // Write log before calling update + await this.writeLog(`context.db.${sanitizedTableName}.update`, blockHeight, defaultLog + '.update', + `Updating objects that match ${JSON.stringify(filterObj)} with values ${JSON.stringify(updateObj)} in table ${tableName} on schema ${schemaName}`); + + // Create DmlHandler if it doesn't exist + dmlHandler = dmlHandler ?? await this.deps.DmlHandler.create(account); + + // Call update with parameters + return await dmlHandler.update(schemaName, tableName, filterObj, updateObj); + }, + upsert: async (objectsToInsert: any, conflictColumns: string[], updateColumns: string[]) => { + // Write log before calling upsert + await this.writeLog(`context.db.${sanitizedTableName}.upsert`, blockHeight, defaultLog + '.upsert', + `Inserting objects with values ${JSON.stringify(objectsToInsert)} into table ${tableName} on schema ${schemaName}. Conflict on columns ${conflictColumns.join(', ')} will update values in columns ${updateColumns.join(', ')}`); + + // Create DmlHandler if it doesn't exist + dmlHandler = dmlHandler ?? await this.deps.DmlHandler.create(account); + + // Call upsert with parameters + return await dmlHandler.upsert(schemaName, tableName, Array.isArray(objectsToInsert) ? objectsToInsert : [objectsToInsert], conflictColumns, updateColumns); + }, + delete: async (filterObj: any) => { + // Write log before calling delete + await this.writeLog(`context.db.${sanitizedTableName}.delete`, blockHeight, defaultLog + '.delete', + `Deleting objects with values ${JSON.stringify(filterObj)} from table ${tableName} on schema ${schemaName}`); + + // Create DmlHandler if it doesn't exist + dmlHandler = dmlHandler ?? await this.deps.DmlHandler.create(account); + + // Call delete with parameters + return await dmlHandler.delete(schemaName, tableName, filterObj); + } + } + }; + + return { + ...prev, + ...funcForTable + }; + }, {}); + return result; + } catch (error) { + console.warn('Caught error when generating context.db methods. Building no functions. You can still use other context object methods.\n', error); + } + + return {}; // Default to empty object if error + } + + async setStatus (functionName: string, blockHeight: number, status: string): Promise { + return await this.runGraphQLQuery( + ` + mutation SetStatus($function_name: String, $status: String) { + insert_indexer_state_one(object: {function_name: $function_name, status: $status, current_block_height: 0 }, on_conflict: { constraint: indexer_state_pkey, update_columns: status }) { + function_name + status + } + } + `, + { + function_name: functionName, + status, + }, + functionName, + blockHeight, + this.DEFAULT_HASURA_ROLE + ); + } + + async writeLog (functionName: string, blockHeight: number, ...message: any[]): Promise { + const parsedMessage: string = message + .map(m => typeof m === 'object' ? JSON.stringify(m) : m) + .join(':'); + + const mutation = + `mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){ + insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) {id} + }`; + + return await this.runGraphQLQuery(mutation, { function_name: functionName, block_height: blockHeight, message: parsedMessage }, + functionName, blockHeight, this.DEFAULT_HASURA_ROLE) + .then((result: any) => { + return result?.insert_indexer_log_entries_one?.id; + }) + .catch((e: any) => { + console.error(`${functionName}: Error writing log`, e); + }); + } + + async writeFunctionState (functionName: string, blockHeight: number, isHistorical: boolean): Promise { + const realTimeMutation: string = + `mutation WriteBlock($function_name: String!, $block_height: numeric!) { + insert_indexer_state( + objects: {current_block_height: $block_height, function_name: $function_name} + on_conflict: {constraint: indexer_state_pkey, update_columns: current_block_height} + ) { + returning { + current_block_height + function_name + } + } + }`; + const historicalMutation: string = ` + mutation WriteBlock($function_name: String!, $block_height: numeric!) { + insert_indexer_state( + objects: {current_historical_block_height: $block_height, current_block_height: 0, function_name: $function_name} + on_conflict: {constraint: indexer_state_pkey, update_columns: current_historical_block_height} + ) { + returning { + current_block_height + current_historical_block_height + function_name + } + } + } + `; + const variables: any = { + function_name: functionName, + block_height: blockHeight, + }; + return await this.runGraphQLQuery(isHistorical ? historicalMutation : realTimeMutation, variables, functionName, blockHeight, this.DEFAULT_HASURA_ROLE) + .catch((e: any) => { + console.error(`${functionName}: Error writing function state`, e); + }); + } + + async runGraphQLQuery (operation: string, variables: any, functionName: string, blockHeight: number, hasuraRoleName: string | null, logError: boolean = true): Promise { + const response: Response = await this.deps.fetch(`${process.env.HASURA_ENDPOINT}/v1/graphql`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Hasura-Use-Backend-Only-Permissions': 'true', + ...(hasuraRoleName && { + 'X-Hasura-Role': hasuraRoleName, + 'X-Hasura-Admin-Secret': process.env.HASURA_ADMIN_SECRET + }), + }, + body: JSON.stringify({ + query: operation, + ...(variables && { variables }), + }), + }); + + const { data, errors } = await response.json(); + + if (response.status !== 200 || errors) { + if (logError) { + console.log(`${functionName}: Error writing graphql `, errors); // temporary extra logging + + const message: string = errors ? errors.map((e: any) => e.message).join(', ') : `HTTP ${response.status} error writing with graphql to indexer storage`; + const mutation: string = + `mutation writeLog($function_name: String!, $block_height: numeric!, $message: String!){ + insert_indexer_log_entries_one(object: {function_name: $function_name, block_height: $block_height, message: $message}) { + id + } + }`; + try { + await this.runGraphQLQuery(mutation, { function_name: functionName, block_height: blockHeight, message }, functionName, blockHeight, this.DEFAULT_HASURA_ROLE, false); + } catch (e) { + console.error(`${functionName}: Error writing log of graphql error`, e); + } + } + throw new Error(`Failed to write graphql, http status: ${response.status}, errors: ${JSON.stringify(errors, null, 2)}`); + } + + return data; + } + + renameUnderscoreFieldsToCamelCase (value: Record): Record { + if (typeof value === 'object' && !Array.isArray(value)) { + // It's a non-null, non-array object, create a replacement with the keys initially-capped + const newValue: any = {}; + for (const key in value) { + const newKey: string = key + .split('_') + .map((word, i) => { + if (i > 0) { + return word.charAt(0).toUpperCase() + word.slice(1); + } + return word; + }) + .join(''); + newValue[newKey] = value[key]; + } + return newValue; + } + return value; + } +} diff --git a/runner/src/metrics.ts b/runner/src/metrics.ts new file mode 100644 index 000000000..65ef6b990 --- /dev/null +++ b/runner/src/metrics.ts @@ -0,0 +1,36 @@ +import express from 'express'; +import promClient from 'prom-client'; + +const UNPROCESSED_STREAM_MESSAGES = new promClient.Gauge({ + name: 'queryapi_runner_unprocessed_stream_messages', + help: 'Number of Redis Stream messages not yet processed', + labelNames: ['indexer', 'type'], +}); + +const EXECUTION_DURATION = new promClient.Gauge({ + name: 'queryapi_runner_execution_duration_milliseconds', + help: 'Time taken to execute an indexer function', + labelNames: ['indexer', 'type'], +}); + +export const METRICS = { + EXECUTION_DURATION, + UNPROCESSED_STREAM_MESSAGES, +}; + +export const startServer = async (): Promise => { + const app = express(); + + // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/50871 + // eslint-disable-next-line @typescript-eslint/no-misused-promises + app.get('/metrics', async (_req, res) => { + res.set('Content-Type', promClient.register.contentType); + + const metrics = await promClient.register.metrics(); + res.send(metrics); + }); + + app.listen(process.env.PORT, () => { + console.log(`Metrics server running on http://localhost:${process.env.PORT}`); + }); +}; diff --git a/runner/src/pg-client.ts b/runner/src/pg-client.ts new file mode 100644 index 000000000..ebca73a49 --- /dev/null +++ b/runner/src/pg-client.ts @@ -0,0 +1,41 @@ +import { Pool, type PoolConfig, type QueryResult, type QueryResultRow } from 'pg'; +import pgFormatModule from 'pg-format'; + +interface ConnectionParams { + user: string + password: string + host: string + port: number | string + database: string +} + +export default class PgClient { + private readonly pgPool: Pool; + public format: typeof pgFormatModule; + + constructor ( + connectionParams: ConnectionParams, + poolConfig: PoolConfig = { max: 10, idleTimeoutMillis: 30000 }, + PgPool: typeof Pool = Pool, + pgFormat: typeof pgFormatModule = pgFormatModule + ) { + this.pgPool = new PgPool({ + user: connectionParams.user, + password: connectionParams.password, + host: connectionParams.host, + port: Number(connectionParams.port), + database: connectionParams.database, + ...poolConfig, + }); + this.format = pgFormat; + } + + async query(query: string, params: any[] = []): Promise> { + const client = await this.pgPool.connect(); + try { + return await (client.query(query, params)); + } finally { + client.release(); + } + } +} diff --git a/runner/src/provisioner/__snapshots__/provisioner.test.ts.snap b/runner/src/provisioner/__snapshots__/provisioner.test.ts.snap new file mode 100644 index 000000000..406ca3cc0 --- /dev/null +++ b/runner/src/provisioner/__snapshots__/provisioner.test.ts.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Provisioner provisionUserApi formats user input before executing the query 1`] = ` +[ + [ + "CREATE DATABASE "databaseName UNION SELECT * FROM users --"", + ], + [ + "CREATE USER morgs_near WITH PASSWORD 'pass; DROP TABLE users;--'", + ], + [ + "GRANT ALL PRIVILEGES ON DATABASE "databaseName UNION SELECT * FROM users --" TO morgs_near", + ], + [ + "REVOKE CONNECT ON DATABASE "databaseName UNION SELECT * FROM users --" FROM PUBLIC", + ], +] +`; diff --git a/runner/src/provisioner/index.ts b/runner/src/provisioner/index.ts new file mode 100644 index 000000000..5a4dbeb8d --- /dev/null +++ b/runner/src/provisioner/index.ts @@ -0,0 +1 @@ +export { default } from './provisioner'; diff --git a/runner/src/provisioner/provisioner.test.ts b/runner/src/provisioner/provisioner.test.ts new file mode 100644 index 000000000..a1c6cbc62 --- /dev/null +++ b/runner/src/provisioner/provisioner.test.ts @@ -0,0 +1,212 @@ +import pgFormat from 'pg-format'; + +import Provisioner from './provisioner'; + +describe('Provisioner', () => { + let pgClient: any; + let hasuraClient: any; + + const tableNames = ['blocks']; + const accountId = 'morgs.near'; + const sanitizedAccountId = 'morgs_near'; + const functionName = 'test-function'; + const sanitizedFunctionName = 'test_function'; + const databaseSchema = 'CREATE TABLE blocks (height numeric)'; + const error = new Error('some error'); + const defaultDatabase = 'default'; + const schemaName = `${sanitizedAccountId}_${sanitizedFunctionName}`; + + const password = 'password'; + const crypto: any = { + randomBytes: () => ({ + toString: () => ({ + slice: () => ({ + replace: () => password, + }), + }), + }), + }; + + beforeEach(() => { + hasuraClient = { + getTableNames: jest.fn().mockReturnValueOnce(tableNames), + trackTables: jest.fn().mockReturnValueOnce(null), + trackForeignKeyRelationships: jest.fn().mockReturnValueOnce(null), + addPermissionsToTables: jest.fn().mockReturnValueOnce(null), + addDatasource: jest.fn().mockReturnValueOnce(null), + runMigrations: jest.fn().mockReturnValueOnce(null), + createSchema: jest.fn().mockReturnValueOnce(null), + doesSourceExist: jest.fn().mockReturnValueOnce(false), + doesSchemaExist: jest.fn().mockReturnValueOnce(false), + untrackTables: jest.fn().mockReturnValueOnce(null), + }; + + pgClient = { + query: jest.fn().mockReturnValue(null), + format: pgFormat, + }; + }); + + describe('isUserApiProvisioned', () => { + it('returns false if datasource doesnt exists', async () => { + hasuraClient.doesSourceExist = jest.fn().mockReturnValueOnce(false); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.isUserApiProvisioned(accountId, functionName)).resolves.toBe(false); + }); + + it('returns false if datasource and schema dont exists', async () => { + hasuraClient.doesSourceExist = jest.fn().mockReturnValueOnce(false); + hasuraClient.doesSchemaExist = jest.fn().mockReturnValueOnce(false); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.isUserApiProvisioned(accountId, functionName)).resolves.toBe(false); + }); + + it('returns true if datasource and schema exists', async () => { + hasuraClient.doesSourceExist = jest.fn().mockReturnValueOnce(true); + hasuraClient.doesSchemaExist = jest.fn().mockReturnValueOnce(true); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.isUserApiProvisioned(accountId, functionName)).resolves.toBe(true); + }); + }); + + describe('provisionUserApi', () => { + it('provisions an API for the user', async () => { + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await provisioner.provisionUserApi(accountId, functionName, databaseSchema); + + expect(pgClient.query.mock.calls).toEqual([ + ['CREATE DATABASE morgs_near'], + ['CREATE USER morgs_near WITH PASSWORD \'password\''], + ['GRANT ALL PRIVILEGES ON DATABASE morgs_near TO morgs_near'], + ['REVOKE CONNECT ON DATABASE morgs_near FROM PUBLIC'], + ]); + expect(hasuraClient.addDatasource).toBeCalledWith(sanitizedAccountId, password, sanitizedAccountId); + expect(hasuraClient.createSchema).toBeCalledWith(sanitizedAccountId, schemaName); + expect(hasuraClient.runMigrations).toBeCalledWith(sanitizedAccountId, schemaName, databaseSchema); + expect(hasuraClient.getTableNames).toBeCalledWith(schemaName, sanitizedAccountId); + expect(hasuraClient.trackTables).toBeCalledWith(schemaName, tableNames, sanitizedAccountId); + expect(hasuraClient.addPermissionsToTables).toBeCalledWith( + schemaName, + sanitizedAccountId, + tableNames, + sanitizedAccountId, + [ + 'select', + 'insert', + 'update', + 'delete' + ] + ); + }); + + it('untracks tables from the previous schema if they exists', async () => { + hasuraClient.doesSchemaExist = jest.fn().mockReturnValueOnce(true); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await provisioner.provisionUserApi(accountId, functionName, databaseSchema); + + expect(hasuraClient.getTableNames).toBeCalledWith(schemaName, defaultDatabase); + expect(hasuraClient.untrackTables).toBeCalledWith(defaultDatabase, schemaName, tableNames); + }); + + it('skips provisioning the datasource if it already exists', async () => { + hasuraClient.doesSourceExist = jest.fn().mockReturnValueOnce(true); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await provisioner.provisionUserApi(accountId, functionName, databaseSchema); + + expect(pgClient.query).not.toBeCalled(); + expect(hasuraClient.addDatasource).not.toBeCalled(); + + expect(hasuraClient.createSchema).toBeCalledWith(sanitizedAccountId, schemaName); + expect(hasuraClient.runMigrations).toBeCalledWith(sanitizedAccountId, schemaName, databaseSchema); + expect(hasuraClient.getTableNames).toBeCalledWith(schemaName, sanitizedAccountId); + expect(hasuraClient.trackTables).toBeCalledWith(schemaName, tableNames, sanitizedAccountId); + expect(hasuraClient.addPermissionsToTables).toBeCalledWith( + schemaName, + sanitizedAccountId, + tableNames, + sanitizedAccountId, + [ + 'select', + 'insert', + 'update', + 'delete' + ] + ); + }); + + it('formats user input before executing the query', async () => { + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await provisioner.createUserDb('morgs_near', 'pass; DROP TABLE users;--', 'databaseName UNION SELECT * FROM users --'); + + expect(pgClient.query.mock.calls).toMatchSnapshot(); + }); + + it('throws an error when it fails to create a postgres db', async () => { + pgClient.query = jest.fn().mockRejectedValue(error); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.provisionUserApi(accountId, functionName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to create user db: some error'); + }); + + it('throws an error when it fails to add the db to hasura', async () => { + hasuraClient.addDatasource = jest.fn().mockRejectedValue(error); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.provisionUserApi(accountId, functionName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to add datasource: some error'); + }); + + it('throws an error when it fails to run migrations', async () => { + hasuraClient.runMigrations = jest.fn().mockRejectedValue(error); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.provisionUserApi(accountId, functionName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to run migrations: some error'); + }); + + it('throws an error when it fails to fetch table names', async () => { + hasuraClient.getTableNames = jest.fn().mockRejectedValue(error); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.provisionUserApi(accountId, functionName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to fetch table names: some error'); + }); + + it('throws an error when it fails to track tables', async () => { + hasuraClient.trackTables = jest.fn().mockRejectedValue(error); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.provisionUserApi(accountId, functionName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to track tables: some error'); + }); + + it('throws an error when it fails to track foreign key relationships', async () => { + hasuraClient.trackForeignKeyRelationships = jest.fn().mockRejectedValue(error); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.provisionUserApi(accountId, functionName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to track foreign key relationships: some error'); + }); + + it('throws an error when it fails to add permissions to tables', async () => { + hasuraClient.addPermissionsToTables = jest.fn().mockRejectedValue(error); + + const provisioner = new Provisioner(hasuraClient, pgClient, crypto); + + await expect(provisioner.provisionUserApi(accountId, functionName, databaseSchema)).rejects.toThrow('Failed to provision endpoint: Failed to add permissions to tables: some error'); + }); + }); +}); diff --git a/runner/src/provisioner/provisioner.ts b/runner/src/provisioner/provisioner.ts new file mode 100644 index 000000000..3634cef91 --- /dev/null +++ b/runner/src/provisioner/provisioner.ts @@ -0,0 +1,150 @@ +import { wrapError } from '../utility'; +import cryptoModule from 'crypto'; +import HasuraClient from '../hasura-client'; +import PgClient from '../pg-client'; + +const DEFAULT_PASSWORD_LENGTH = 16; + +const sharedPgClient = new PgClient({ + user: process.env.PGUSER, + password: process.env.PGPASSWORD, + database: process.env.PGDATABASE, + host: process.env.PGHOST, + port: Number(process.env.PGPORT), +}); + +export default class Provisioner { + constructor ( + private readonly hasuraClient: HasuraClient = new HasuraClient(), + private readonly pgClient: PgClient = sharedPgClient, + private readonly crypto: typeof cryptoModule = cryptoModule, + ) { + this.hasuraClient = hasuraClient; + this.pgClient = pgClient; + this.crypto = crypto; + } + + generatePassword (length: number = DEFAULT_PASSWORD_LENGTH): string { + return this.crypto + .randomBytes(length) + .toString('base64') + .slice(0, length) + .replace(/\+/g, '0') + .replace(/\//g, '0'); + } + + async createDatabase (name: string): Promise { + await this.pgClient.query(this.pgClient.format('CREATE DATABASE %I', name)); + } + + async createUser (name: string, password: string): Promise { + await this.pgClient.query(this.pgClient.format('CREATE USER %I WITH PASSWORD %L', name, password)); + } + + async restrictUserToDatabase (databaseName: string, userName: string): Promise { + await this.pgClient.query(this.pgClient.format('GRANT ALL PRIVILEGES ON DATABASE %I TO %I', databaseName, userName)); + await this.pgClient.query(this.pgClient.format('REVOKE CONNECT ON DATABASE %I FROM PUBLIC', databaseName)); + } + + async createUserDb (userName: string, password: string, databaseName: string): Promise { + await wrapError( + async () => { + await this.createDatabase(databaseName); + await this.createUser(userName, password); + await this.restrictUserToDatabase(databaseName, userName); + }, + 'Failed to create user db' + ); + } + + async isUserApiProvisioned (accountId: string, functionName: string): Promise { + const sanitizedAccountId = this.replaceSpecialChars(accountId); + const sanitizedFunctionName = this.replaceSpecialChars(functionName); + + const databaseName = sanitizedAccountId; + const schemaName = `${sanitizedAccountId}_${sanitizedFunctionName}`; + + const sourceExists = await this.hasuraClient.doesSourceExist(databaseName); + if (!sourceExists) { + return false; + } + + const schemaExists = await this.hasuraClient.doesSchemaExist(databaseName, schemaName); + + return schemaExists; + } + + async createSchema (databaseName: string, schemaName: string): Promise { + return await wrapError(async () => await this.hasuraClient.createSchema(databaseName, schemaName), 'Failed to create schema'); + } + + async runMigrations (databaseName: string, schemaName: string, migration: any): Promise { + return await wrapError(async () => await this.hasuraClient.runMigrations(databaseName, schemaName, migration), 'Failed to run migrations'); + } + + async getTableNames (schemaName: string, databaseName: string): Promise { + return await wrapError(async () => await this.hasuraClient.getTableNames(schemaName, databaseName), 'Failed to fetch table names'); + } + + async trackTables (schemaName: string, tableNames: string[], databaseName: string): Promise { + return await wrapError(async () => await this.hasuraClient.trackTables(schemaName, tableNames, databaseName), 'Failed to track tables'); + } + + async addPermissionsToTables (schemaName: string, databaseName: string, tableNames: string[], roleName: string, permissions: string[]): Promise { + return await wrapError(async () => await this.hasuraClient.addPermissionsToTables( + schemaName, + databaseName, + tableNames, + roleName, + permissions + ), 'Failed to add permissions to tables'); + } + + async trackForeignKeyRelationships (schemaName: string, databaseName: string): Promise { + return await wrapError(async () => await this.hasuraClient.trackForeignKeyRelationships(schemaName, databaseName), 'Failed to track foreign key relationships'); + } + + async addDatasource (userName: string, password: string, databaseName: string): Promise { + return await wrapError(async () => await this.hasuraClient.addDatasource(userName, password, databaseName), 'Failed to add datasource'); + } + + replaceSpecialChars (str: string): string { + return str.replaceAll(/[.-]/g, '_'); + } + + async provisionUserApi (accountId: string, functionName: string, databaseSchema: any): Promise { // replace any with actual type + const sanitizedAccountId = this.replaceSpecialChars(accountId); + const sanitizedFunctionName = this.replaceSpecialChars(functionName); + + const databaseName = sanitizedAccountId; + const userName = sanitizedAccountId; + const schemaName = `${sanitizedAccountId}_${sanitizedFunctionName}`; + + await wrapError( + async () => { + if (!await this.hasuraClient.doesSourceExist(databaseName)) { + const password = this.generatePassword(); + await this.createUserDb(userName, password, databaseName); + await this.addDatasource(userName, password, databaseName); + } + + // Untrack tables from old schema to prevent conflicts with new DB + if (await this.hasuraClient.doesSchemaExist(HasuraClient.DEFAULT_DATABASE, schemaName)) { + const tableNames = await this.getTableNames(schemaName, HasuraClient.DEFAULT_DATABASE); + await this.hasuraClient.untrackTables(HasuraClient.DEFAULT_DATABASE, schemaName, tableNames); + } + + await this.createSchema(databaseName, schemaName); + await this.runMigrations(databaseName, schemaName, databaseSchema); + + const tableNames = await this.getTableNames(schemaName, databaseName); + await this.trackTables(schemaName, tableNames, databaseName); + + await this.trackForeignKeyRelationships(schemaName, databaseName); + + await this.addPermissionsToTables(schemaName, databaseName, tableNames, userName, ['select', 'insert', 'update', 'delete']); + }, + 'Failed to provision endpoint' + ); + } +} diff --git a/runner/src/redis-client/index.ts b/runner/src/redis-client/index.ts new file mode 100644 index 000000000..efa0f96e7 --- /dev/null +++ b/runner/src/redis-client/index.ts @@ -0,0 +1 @@ +export { default } from './redis-client'; diff --git a/runner/src/redis-client/redis-client.test.ts b/runner/src/redis-client/redis-client.test.ts new file mode 100644 index 000000000..85588ccab --- /dev/null +++ b/runner/src/redis-client/redis-client.test.ts @@ -0,0 +1,84 @@ +import RedisClient from './redis-client'; + +describe('RedisClient', () => { + it('returns the first message', async () => { + const mockClient = { + on: jest.fn(), + connect: jest.fn().mockResolvedValue(null), + xRead: jest.fn().mockResolvedValue(null), + } as any; + + const client = new RedisClient(mockClient); + + const message = await client.getNextStreamMessage('streamKey'); + + expect(mockClient.xRead).toHaveBeenCalledWith( + { key: 'streamKey', id: '0' }, + { COUNT: 1 } + ); + expect(message).toBeUndefined(); + }); + + it('deletes the stream message', async () => { + const mockClient = { + on: jest.fn(), + connect: jest.fn().mockResolvedValue(null), + xDel: jest.fn().mockResolvedValue(null), + } as any; + + const client = new RedisClient(mockClient); + + await client.deleteStreamMessage('streamKey', '1-1'); + + expect(mockClient.xDel).toHaveBeenCalledWith('streamKey', '1-1'); + }); + + it('returns the range of messages after the passed id', async () => { + const mockClient = { + on: jest.fn(), + connect: jest.fn().mockResolvedValue(null), + xRange: jest.fn().mockResolvedValue([ + 'data' + ]), + } as any; + + const client = new RedisClient(mockClient); + + const unprocessedMessages = await client.getUnprocessedStreamMessages('streamKey'); + + expect(mockClient.xRange).toHaveBeenCalledWith('streamKey', '0', '+'); + expect(unprocessedMessages).toEqual([ + 'data' + ]); + }); + + it('returns stream storage data', async () => { + const mockClient = { + on: jest.fn(), + connect: jest.fn().mockResolvedValue(null), + get: jest.fn().mockResolvedValue(JSON.stringify({ account_id: '123', function_name: 'testFunc' })), + } as any; + + const client = new RedisClient(mockClient); + + const storageData = await client.getStreamStorage('streamKey'); + + expect(mockClient.get).toHaveBeenCalledWith('streamKey:storage'); + expect(storageData).toEqual({ account_id: '123', function_name: 'testFunc' }); + }); + + it('returns the list of streams', async () => { + const mockClient = { + on: jest.fn(), + connect: jest.fn().mockResolvedValue(null), + sMembers: jest.fn().mockResolvedValue(['streamKey1', 'streamKey2']), + } as any; + + const client = new RedisClient(mockClient); + + const streams = await client.getStreams(); + + expect(mockClient.sMembers).toHaveBeenCalledWith('streams'); + expect(streams).toEqual(['streamKey1', 'streamKey2']); + }); +}); diff --git a/runner/src/redis-client/redis-client.ts b/runner/src/redis-client/redis-client.ts new file mode 100644 index 000000000..9caa66226 --- /dev/null +++ b/runner/src/redis-client/redis-client.ts @@ -0,0 +1,86 @@ +import { createClient, type RedisClientType } from 'redis'; + +interface StreamMessage { + id: string + message: { + block_height: string + } +} + +interface StreamStorage { + account_id: string + function_name: string + code: string + schema: string +} + +type StreamType = 'historical' | 'real-time'; + +export default class RedisClient { + SMALLEST_STREAM_ID = '0'; + LARGEST_STREAM_ID = '+'; + STREAMS_SET_KEY = 'streams'; + + constructor ( + private readonly client: RedisClientType = createClient({ url: process.env.REDIS_CONNECTION_STRING }) + ) { + client.on('error', (err) => { console.log('Redis Client Error', err); }); + client.connect().catch(console.error); + } + + private generateStorageKey (streamkey: string): string { + return `${streamkey}:storage`; + }; + + getStreamType (streamKey: string): StreamType { + if (streamKey.endsWith(':historical:stream')) { + return 'historical'; + } + return 'real-time'; + } + + async disconnect (): Promise { + await this.client.disconnect(); + } + + async getNextStreamMessage ( + streamKey: string, + ): Promise { + const results = await this.client.xRead( + { key: streamKey, id: this.SMALLEST_STREAM_ID }, + { COUNT: 1 } + ); + + return results?.[0].messages as StreamMessage[]; + }; + + async deleteStreamMessage ( + streamKey: string, + id: string, + ): Promise { + await this.client.xDel(streamKey, id); + }; + + async getUnprocessedStreamMessages ( + streamKey: string, + ): Promise { + const results = await this.client.xRange(streamKey, this.SMALLEST_STREAM_ID, this.LARGEST_STREAM_ID); + + return results as StreamMessage[]; + }; + + async getStreamStorage (streamKey: string): Promise { + const storageKey = this.generateStorageKey(streamKey); + const results = await this.client.get(storageKey); + + if (results === null) { + throw new Error(`${storageKey} does not have any data`); + } + + return JSON.parse(results); + }; + + async getStreams (): Promise { + return await this.client.sMembers(this.STREAMS_SET_KEY); + } +} diff --git a/runner/src/stream-handler/index.ts b/runner/src/stream-handler/index.ts new file mode 100644 index 000000000..1b4a410f1 --- /dev/null +++ b/runner/src/stream-handler/index.ts @@ -0,0 +1 @@ +export { default } from './stream-handler'; diff --git a/runner/src/stream-handler/stream-handler.ts b/runner/src/stream-handler/stream-handler.ts new file mode 100644 index 000000000..7e1fe2237 --- /dev/null +++ b/runner/src/stream-handler/stream-handler.ts @@ -0,0 +1,29 @@ +import path from 'path'; +import { Worker, isMainThread } from 'worker_threads'; + +import { type Message } from './types'; +import { METRICS } from '../metrics'; + +export default class StreamHandler { + private readonly worker?: Worker; + + constructor ( + streamKey: string + ) { + if (isMainThread) { + this.worker = new Worker(path.join(__dirname, 'worker.js'), { + workerData: { + streamKey, + }, + }); + + this.worker.on('message', this.handleMessage); + } else { + throw new Error('StreamHandler should not be instantiated in a worker thread'); + } + } + + private handleMessage (message: Message): void { + METRICS[message.type].labels(message.labels).set(message.value); + } +} diff --git a/runner/src/stream-handler/types.ts b/runner/src/stream-handler/types.ts new file mode 100644 index 000000000..945248e1b --- /dev/null +++ b/runner/src/stream-handler/types.ts @@ -0,0 +1,9 @@ +import { type METRICS } from '../metrics'; + +interface Metric { + type: keyof typeof METRICS + labels: Record + value: number +}; + +export type Message = Metric; diff --git a/runner/src/stream-handler/worker.ts b/runner/src/stream-handler/worker.ts new file mode 100644 index 000000000..a80e854ee --- /dev/null +++ b/runner/src/stream-handler/worker.ts @@ -0,0 +1,75 @@ +import { isMainThread, parentPort, workerData } from 'worker_threads'; + +import Indexer from '../indexer'; +import RedisClient from '../redis-client'; +import { type Message } from './types'; + +if (isMainThread) { + throw new Error('Worker should not be run on main thread'); +} + +const indexer = new Indexer('mainnet'); +const redisClient = new RedisClient(); + +const sleep = async (ms: number): Promise => { await new Promise((resolve) => setTimeout(resolve, ms)); }; + +void (async function main () { + const { streamKey } = workerData; + + console.log('Started processing stream: ', streamKey); + + let indexerName = ''; + + while (true) { + try { + const startTime = performance.now(); + const streamType = redisClient.getStreamType(streamKey); + + const messages = await redisClient.getNextStreamMessage(streamKey); + const indexerConfig = await redisClient.getStreamStorage(streamKey); + + indexerName = `${indexerConfig.account_id}/${indexerConfig.function_name}`; + + if (messages == null) { + await sleep(1000); + continue; + } + + const [{ id, message }] = messages; + + const functions = { + [indexerName]: { + account_id: indexerConfig.account_id, + function_name: indexerConfig.function_name, + code: indexerConfig.code, + schema: indexerConfig.schema, + provisioned: false, + }, + }; + await indexer.runFunctions(Number(message.block_height), functions, false, { + provision: true, + }); + + await redisClient.deleteStreamMessage(streamKey, id); + + const unprocessedMessages = await redisClient.getUnprocessedStreamMessages(streamKey); + + parentPort?.postMessage({ + type: 'UNPROCESSED_STREAM_MESSAGES', + labels: { indexer: indexerName, type: streamType }, + value: unprocessedMessages?.length ?? 0, + } satisfies Message); + + parentPort?.postMessage({ + type: 'EXECUTION_DURATION', + labels: { indexer: indexerName, type: streamType }, + value: performance.now() - startTime, + } satisfies Message); + + console.log(`Success: ${indexerName}`); + } catch (err) { + await sleep(10000); + console.log(`Failed: ${indexerName}`, err); + } + } +})(); diff --git a/runner/src/utility.ts b/runner/src/utility.ts new file mode 100644 index 000000000..33262f408 --- /dev/null +++ b/runner/src/utility.ts @@ -0,0 +1,13 @@ +import VError from 'verror'; + +export async function wrapError (fn: () => Promise, errorMessage: string): Promise { + try { + return await fn(); + } catch (error) { + console.log(error); + if (error instanceof Error) { + throw new VError(error, errorMessage); + } + throw new VError(errorMessage); + } +} diff --git a/runner/tsconfig.json b/runner/tsconfig.json new file mode 100644 index 000000000..c3aead636 --- /dev/null +++ b/runner/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2018", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "lib": ["es2021"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDir": "src", /* Specify the root folder within your source files. */ + "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + "resolveJsonModule": true, /* Enable importing .json files. */ + "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + "outDir": "dist", /* Specify an output folder for all emitted files. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +}