From a10c9544dd26071c1daa47ee67f1351b03742bce Mon Sep 17 00:00:00 2001 From: rkamysz Date: Mon, 7 Nov 2022 21:38:14 +0100 Subject: [PATCH] implement components - contains bugs --- .eslintignore | 7 + .eslintrc.js | 27 + .gitignore | 6 + .npmignore | 0 .npmrc | 1 + .prettierignore | 4 + .prettierrc.json | 7 + LICENSE | 21 + README.md | 10 +- jest.config.unit.js | 30 + nodemon.json | 7 + package.json | 48 + scripts/deploy.sh | 39 + src/block-range/block-range.broadcast.ts | 41 + src/block-range/block-range.config.ts | 22 + src/block-range/block-range.mapper.ts | 60 + src/block-range/block-range.task-input.ts | 36 + src/block-range/block-range.task.ts | 168 + src/block-range/block-range.types.ts | 20 + src/block-range/block-range.utils.ts | 100 + src/block-range/index.ts | 1 + src/block-range/start-block-range.ts | 145 + ...block-range-scan.mongo.source.unit.test.ts | 279 ++ .../block-range-scan.repository.unit.test.ts | 207 + .../__tests__/block-range-scan.unit.test.ts | 213 + .../block-range-scan.mongo.source.ts | 198 + .../block-range-scan.repository.ts | 84 + .../block-range-scanner/block-range-scan.ts | 235 ++ .../block-range-scanner.config.ts | 5 + .../block-range-scanner.dtos.ts | 16 + .../block-range-scanner.errors.ts | 15 + .../block-range-scanner.ts | 41 + .../block-range-scanner.utils.ts | 23 + src/common/block-range-scanner/index.ts | 8 + src/common/block-state/block-state.source.ts | 33 + src/common/block-state/block-state.ts | 17 + src/common/block-state/block-state.utils.ts | 12 + src/common/block-state/index.ts | 3 + .../abi/__tests__/abi.unit.test.ts | 185 + .../block-content/abi/abi-action.ts | 40 + .../block-content/abi/abi-error-message.ts | 34 + .../block-content/abi/abi-extension.ts | 31 + .../block-content/abi/abi-struct.ts | 70 + .../blockchain/block-content/abi/abi-table.ts | 46 + .../blockchain/block-content/abi/abi-type.ts | 40 + .../block-content/abi/abi-variant.ts | 31 + .../blockchain/block-content/abi/abi.dtos.ts | 136 + .../blockchain/block-content/abi/abi.ts | 119 + .../blockchain/block-content/abi/index.ts | 10 + .../block-content/abi/ricardian-clause.ts | 31 + .../__tests__/action-trace.unit.test.ts | 63 + .../action-trace/action-trace.dtos.ts | 44 + .../action-trace/action-trace.ts | 127 + .../block-content/action-trace/index.ts | 2 + .../block/__tests__/block.unit.test.ts | 47 + .../block-content/block/block.dtos.ts | 15 + .../blockchain/block-content/block/block.ts | 54 + .../blockchain/block-content/block/index.ts | 2 + .../delta/__tests__/delta.unit.test.ts | 14 + .../block-content/delta/delta.dtos.ts | 11 + .../blockchain/block-content/delta/delta.ts | 33 + .../blockchain/block-content/delta/index.ts | 2 + src/common/blockchain/block-content/index.ts | 6 + .../trace/__tests__/trace.unit.test.ts | 68 + .../blockchain/block-content/trace/index.ts | 2 + .../block-content/trace/trace.dtos.ts | 33 + .../blockchain/block-content/trace/trace.ts | 107 + .../__tests__/transaction.unit.test.ts | 44 + .../block-content/transaction/index.ts | 2 + .../transaction/transaction.dtos.ts | 14 + .../block-content/transaction/transaction.ts | 63 + .../block-reader.source.unit.test.ts | 219 + .../__tests__/block-reader.utils.unit.test.ts | 47 + .../block-reader/block-reader.config.ts | 5 + .../block-reader/block-reader.dtos.ts | 14 + .../block-reader/block-reader.enums.ts | 6 + .../block-reader/block-reader.errors.ts | 37 + .../block-reader/block-reader.source.ts | 119 + .../blockchain/block-reader/block-reader.ts | 209 + .../block-reader/block-reader.types.ts | 16 + .../block-reader/block-reader.utils.ts | 54 + src/common/blockchain/block-reader/index.ts | 9 + .../block-reader.message.unit.test.ts | 42 + .../get-blocks-ack.request.unit.test.ts | 21 + .../__tests__/get-blocks.request.unit.test.ts | 35 + .../__tests__/get-blocks.result.unit.test.ts | 54 + .../models/block-reader.message.ts | 36 + .../models/get-blocks-ack.request.ts | 19 + .../block-reader/models/get-blocks.request.ts | 52 + .../blockchain/block-reader/models/index.ts | 4 + .../block-reader/models/received-block.ts | 75 + src/common/blockchain/blockchain.utils.ts | 19 + src/common/blockchain/index.ts | 1 + .../broadcast/amq/broadcast.amq.client.ts | 372 ++ .../broadcast/amq/broadcast.amq.message.ts | 32 + src/common/broadcast/broadcast.errors.ts | 5 + src/common/broadcast/broadcast.types.ts | 51 + src/common/broadcast/broadcast.utils.ts | 27 + src/common/broadcast/index.ts | 7 + src/common/enums.ts | 5 + src/common/workers/index.ts | 6 + src/common/workers/worker-message.ts | 106 + src/common/workers/worker-pool.ts | 100 + src/common/workers/worker-task.ts | 55 + src/common/workers/worker.enums.ts | 4 + src/common/workers/worker.errors.ts | 13 + src/common/workers/worker.ts | 36 + src/common/workers/worker.utils.ts | 22 + src/filler/filler.config.ts | 19 + src/filler/index.ts | 2 + src/filler/start-filler.ts | 182 + src/index.ts | 9 + src/processor/index.ts | 3 + src/processor/processor.broadcast.ts | 49 + src/processor/processor.config.ts | 6 + src/processor/start-processor.ts | 76 + src/processor/tasks/delta-processor.mapper.ts | 62 + .../tasks/delta-processor.task-input.ts | 68 + src/processor/tasks/trace-processor.mapper.ts | 83 + .../tasks/trace-processor.task-input.ts | 65 + tsconfig.build.json | 6 + tsconfig.json | 18 + yarn.lock | 3598 +++++++++++++++++ 123 files changed, 9894 insertions(+), 1 deletion(-) create mode 100644 .eslintignore create mode 100644 .eslintrc.js create mode 100644 .npmignore create mode 100644 .npmrc create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 LICENSE create mode 100644 jest.config.unit.js create mode 100644 nodemon.json create mode 100644 package.json create mode 100755 scripts/deploy.sh create mode 100644 src/block-range/block-range.broadcast.ts create mode 100644 src/block-range/block-range.config.ts create mode 100644 src/block-range/block-range.mapper.ts create mode 100644 src/block-range/block-range.task-input.ts create mode 100644 src/block-range/block-range.task.ts create mode 100644 src/block-range/block-range.types.ts create mode 100644 src/block-range/block-range.utils.ts create mode 100644 src/block-range/index.ts create mode 100644 src/block-range/start-block-range.ts create mode 100644 src/common/block-range-scanner/__tests__/block-range-scan.mongo.source.unit.test.ts create mode 100644 src/common/block-range-scanner/__tests__/block-range-scan.repository.unit.test.ts create mode 100644 src/common/block-range-scanner/__tests__/block-range-scan.unit.test.ts create mode 100644 src/common/block-range-scanner/block-range-scan.mongo.source.ts create mode 100644 src/common/block-range-scanner/block-range-scan.repository.ts create mode 100644 src/common/block-range-scanner/block-range-scan.ts create mode 100644 src/common/block-range-scanner/block-range-scanner.config.ts create mode 100644 src/common/block-range-scanner/block-range-scanner.dtos.ts create mode 100644 src/common/block-range-scanner/block-range-scanner.errors.ts create mode 100644 src/common/block-range-scanner/block-range-scanner.ts create mode 100644 src/common/block-range-scanner/block-range-scanner.utils.ts create mode 100644 src/common/block-range-scanner/index.ts create mode 100644 src/common/block-state/block-state.source.ts create mode 100644 src/common/block-state/block-state.ts create mode 100644 src/common/block-state/block-state.utils.ts create mode 100644 src/common/block-state/index.ts create mode 100644 src/common/blockchain/block-content/abi/__tests__/abi.unit.test.ts create mode 100644 src/common/blockchain/block-content/abi/abi-action.ts create mode 100644 src/common/blockchain/block-content/abi/abi-error-message.ts create mode 100644 src/common/blockchain/block-content/abi/abi-extension.ts create mode 100644 src/common/blockchain/block-content/abi/abi-struct.ts create mode 100644 src/common/blockchain/block-content/abi/abi-table.ts create mode 100644 src/common/blockchain/block-content/abi/abi-type.ts create mode 100644 src/common/blockchain/block-content/abi/abi-variant.ts create mode 100644 src/common/blockchain/block-content/abi/abi.dtos.ts create mode 100644 src/common/blockchain/block-content/abi/abi.ts create mode 100644 src/common/blockchain/block-content/abi/index.ts create mode 100644 src/common/blockchain/block-content/abi/ricardian-clause.ts create mode 100644 src/common/blockchain/block-content/action-trace/__tests__/action-trace.unit.test.ts create mode 100644 src/common/blockchain/block-content/action-trace/action-trace.dtos.ts create mode 100644 src/common/blockchain/block-content/action-trace/action-trace.ts create mode 100644 src/common/blockchain/block-content/action-trace/index.ts create mode 100644 src/common/blockchain/block-content/block/__tests__/block.unit.test.ts create mode 100644 src/common/blockchain/block-content/block/block.dtos.ts create mode 100644 src/common/blockchain/block-content/block/block.ts create mode 100644 src/common/blockchain/block-content/block/index.ts create mode 100644 src/common/blockchain/block-content/delta/__tests__/delta.unit.test.ts create mode 100644 src/common/blockchain/block-content/delta/delta.dtos.ts create mode 100644 src/common/blockchain/block-content/delta/delta.ts create mode 100644 src/common/blockchain/block-content/delta/index.ts create mode 100644 src/common/blockchain/block-content/index.ts create mode 100644 src/common/blockchain/block-content/trace/__tests__/trace.unit.test.ts create mode 100644 src/common/blockchain/block-content/trace/index.ts create mode 100644 src/common/blockchain/block-content/trace/trace.dtos.ts create mode 100644 src/common/blockchain/block-content/trace/trace.ts create mode 100644 src/common/blockchain/block-content/transaction/__tests__/transaction.unit.test.ts create mode 100644 src/common/blockchain/block-content/transaction/index.ts create mode 100644 src/common/blockchain/block-content/transaction/transaction.dtos.ts create mode 100644 src/common/blockchain/block-content/transaction/transaction.ts create mode 100644 src/common/blockchain/block-reader/__tests__/block-reader.source.unit.test.ts create mode 100644 src/common/blockchain/block-reader/__tests__/block-reader.utils.unit.test.ts create mode 100644 src/common/blockchain/block-reader/block-reader.config.ts create mode 100644 src/common/blockchain/block-reader/block-reader.dtos.ts create mode 100644 src/common/blockchain/block-reader/block-reader.enums.ts create mode 100644 src/common/blockchain/block-reader/block-reader.errors.ts create mode 100644 src/common/blockchain/block-reader/block-reader.source.ts create mode 100644 src/common/blockchain/block-reader/block-reader.ts create mode 100644 src/common/blockchain/block-reader/block-reader.types.ts create mode 100644 src/common/blockchain/block-reader/block-reader.utils.ts create mode 100644 src/common/blockchain/block-reader/index.ts create mode 100644 src/common/blockchain/block-reader/models/__tests__/block-reader.message.unit.test.ts create mode 100644 src/common/blockchain/block-reader/models/__tests__/get-blocks-ack.request.unit.test.ts create mode 100644 src/common/blockchain/block-reader/models/__tests__/get-blocks.request.unit.test.ts create mode 100644 src/common/blockchain/block-reader/models/__tests__/get-blocks.result.unit.test.ts create mode 100644 src/common/blockchain/block-reader/models/block-reader.message.ts create mode 100644 src/common/blockchain/block-reader/models/get-blocks-ack.request.ts create mode 100644 src/common/blockchain/block-reader/models/get-blocks.request.ts create mode 100644 src/common/blockchain/block-reader/models/index.ts create mode 100644 src/common/blockchain/block-reader/models/received-block.ts create mode 100644 src/common/blockchain/blockchain.utils.ts create mode 100644 src/common/blockchain/index.ts create mode 100644 src/common/broadcast/amq/broadcast.amq.client.ts create mode 100644 src/common/broadcast/amq/broadcast.amq.message.ts create mode 100644 src/common/broadcast/broadcast.errors.ts create mode 100644 src/common/broadcast/broadcast.types.ts create mode 100644 src/common/broadcast/broadcast.utils.ts create mode 100644 src/common/broadcast/index.ts create mode 100644 src/common/enums.ts create mode 100644 src/common/workers/index.ts create mode 100644 src/common/workers/worker-message.ts create mode 100644 src/common/workers/worker-pool.ts create mode 100644 src/common/workers/worker-task.ts create mode 100644 src/common/workers/worker.enums.ts create mode 100644 src/common/workers/worker.errors.ts create mode 100644 src/common/workers/worker.ts create mode 100644 src/common/workers/worker.utils.ts create mode 100644 src/filler/filler.config.ts create mode 100644 src/filler/index.ts create mode 100644 src/filler/start-filler.ts create mode 100644 src/index.ts create mode 100644 src/processor/index.ts create mode 100644 src/processor/processor.broadcast.ts create mode 100644 src/processor/processor.config.ts create mode 100644 src/processor/start-processor.ts create mode 100644 src/processor/tasks/delta-processor.mapper.ts create mode 100644 src/processor/tasks/delta-processor.task-input.ts create mode 100644 src/processor/tasks/trace-processor.mapper.ts create mode 100644 src/processor/tasks/trace-processor.task-input.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json create mode 100644 yarn.lock diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..99327f9 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +.eslintrc.js +jest.config.unit.js +build/ +coverage/ +node_modules/ +src/**/__tests__/ +src/**/__mocks__/ diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..6b8959b --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,27 @@ +module.exports = { + parser: "@typescript-eslint/parser", + parserOptions: { + tsconfigRootDir: __dirname, + project: [`./tsconfig.json`], + ecmaVersion: 12, + sourceType: "module" + }, + root: true, + extends: [ + "eslint:recommended", + "prettier", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking" + ], + plugins: ["@typescript-eslint"], + env: { + es2021: true, + node: true + }, + ignorePatterns: ["build", "node_modules", "scripts", "__tests__"], + rules: { + "no-case-declarations": "warn", + "no-unused-vars": "off", + "no-constant-condition": "warn" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6704566..881fc61 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,9 @@ dist # TernJS port file .tern-port + +build + +.jest + +coverage \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..e69de29 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..8a09c89 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@alien-worlds:registry=https://npm.pkg.github.com \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..08df95b --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +build/ +node_modules/ +scripts/ +coverage/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..d1e8733 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "arrowParens": "avoid", + "printWidth": 90 +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..48e7205 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Alien Worlds + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4fe293f..279e997 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# api-history-tools \ No newline at end of file +# api-history-tools + +### Processor + +Lorem + +### Block Range + +Lorem \ No newline at end of file diff --git a/jest.config.unit.js b/jest.config.unit.js new file mode 100644 index 0000000..5776a5c --- /dev/null +++ b/jest.config.unit.js @@ -0,0 +1,30 @@ +module.exports = { + clearMocks: true, + collectCoverage: true, + coverageDirectory: 'coverage', + verbose: true, + preset: 'ts-jest', + testEnvironment: 'jest-environment-node', + testMatch: ['**/__tests__/**/*.unit.test.ts'], + collectCoverageFrom: [ + 'src/**/*.ts', + '!src/**/index.ts', + '!src/**/*.config.ts', + '!src/**/config.ts', + '!**/query-model.ts', + '!**/mapper.ts', + '!**/use-case.ts', + '!**/*.repository.ts', + '!**/*.fixture.ts', + '!**/*.mock.ts', + '!**/*.error.ts', + '!**/__tests__/**', + '!**/__mocks__/**', + '!src/**/*.enums.ts', + '!src/**/*.dtos.ts', + '!src/**/*.types.ts', + '!src/**/worker.ts', + '!src/**/repository.ts', + '!src/api/*', + ], +}; diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..ef9f69f --- /dev/null +++ b/nodemon.json @@ -0,0 +1,7 @@ +{ + "verbose": true, + "watch": ["src/**/*.ts"], + "execMap": { + "ts": "node -r ts-node/register" + } + } \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6866182 --- /dev/null +++ b/package.json @@ -0,0 +1,48 @@ +{ + "name": "@alien-worlds/api-history-tools", + "version": "0.0.1", + "description": "", + "engines": { + "node": ">=17.3.0 <18.0.0", + "npm": ">=8.3.0 <9.0.0" + }, + "packageManager": "yarn@3.2.3", + "main": "build/index.js", + "types": "build/index.d.ts", + "files": [ + "build" + ], + "scripts": { + "test:unit": "jest --config=jest.config.unit.js", + "clean": "rm -rf ./build", + "build": "yarn clean && tsc -b", + "prepublish": "yarn clean && tsc --project tsconfig.build.json", + "lint": "eslint . --ext .ts", + "lint-fix": "eslint . --ext .ts --fix", + "format-check": "prettier --check \"src/\"", + "format": "prettier --write \"src/\"" + }, + "license": "ISC", + "devDependencies": { + "@types/jest": "^27.0.3", + "@types/node": "^18.7.14", + "@typescript-eslint/eslint-plugin": "^5.37.0", + "@typescript-eslint/parser": "^5.37.0", + "eslint": "^8.23.1", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^27.4.5", + "prettier": "^2.7.1", + "ts-jest": "^27.1.3", + "typescript": "^4.8.2" + }, + "dependencies": { + "@alien-worlds/api-core": "^0.0.27", + "amqplib": "^0.10.3", + "eosjs": "^22.1.0", + "node-fetch": "^3.2.10", + "reflect-metadata": "^0.1.13", + "text-encoding": "^0.7.0", + "ts-node": "^10.9.1" + } +} diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..182aa5a --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +error_exit() +{ + echo "Error: $1" + exit 1 +} + +# major | minor | patch +TYPE=patch + +if [[ $1 =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] + then + TYPE=$1 +elif [[ $1 == "major" ]] || [[ $1 == "minor" ]] || [[ $1 == "patch" ]] + then + TYPE=$1 +elif [[ $1 == "" ]] + then + TYPE=patch +else + printf "Unknown update type \"%s\".\n- Please use \"major\" | \"minor\" | \"patch\"\n" "$1" + exit 1; +fi + +# Build package +yarn prepublish || error_exit "Prepublish failed" + +# Pull latest changes +git pull || error_exit "Pulling failed" + +# Update version and commit +npm version $TYPE || error_exit "Failed to set a new version of the package" + +# Push changed package.json +git push origin || error_exit "Failed to submit changes to the repository" + +# publish package +npm publish || error_exit "Failed to publish a new version" \ No newline at end of file diff --git a/src/block-range/block-range.broadcast.ts b/src/block-range/block-range.broadcast.ts new file mode 100644 index 0000000..2456b66 --- /dev/null +++ b/src/block-range/block-range.broadcast.ts @@ -0,0 +1,41 @@ +import { + BroadcastAmqClient, + BroadcastMessage, + BroadcastMessageContentMapper, +} from '../common/broadcast'; +import { BlockRangeBroadcastMapper } from './block-range.mapper'; +import { BlockRangeTaskInput } from './block-range.task-input'; + +export abstract class BlockRangeBroadcastEmmiter { + public abstract sendMessage(data: BlockRangeTaskInput): Promise; +} + +export class BlockRangeBroadcast implements BlockRangeBroadcastEmmiter { + constructor(private client: BroadcastAmqClient, private channel: string) {} + + public sendMessage(data: BlockRangeTaskInput): Promise { + return this.client.sendMessage(this.channel, data); + } + + public onMessage( + handler: (message: BroadcastMessage) => void + ): void { + return this.client.onMessage(this.channel, handler); + } +} + +export const createBlockRangeBroadcastOptions = ( + mapper?: BroadcastMessageContentMapper +) => { + return { + prefetch: 1, + queues: [ + { + name: 'block_range', + options: { durable: false }, + mapper: mapper || new BlockRangeBroadcastMapper(), + noAck: true, + }, + ], + }; +}; diff --git a/src/block-range/block-range.config.ts b/src/block-range/block-range.config.ts new file mode 100644 index 0000000..f6da479 --- /dev/null +++ b/src/block-range/block-range.config.ts @@ -0,0 +1,22 @@ +import { BlockRangeScanConfig } from '../common/block-range-scanner'; +import { BlockReaderConfig } from '../common/blockchain/block-reader'; + +export type BlockRangeConfig = { + broadcast: { + url: string; + }; + reader: BlockReaderConfig; + mongo: { url: string; dbName: string }; + scanner: BlockRangeScanConfig; + threads: number; +}; + +export const blockRangeConfig: BlockRangeConfig = { + broadcast: { + url: '', + }, + reader: { shipEndpoints: [] }, + mongo: { url: '', dbName: '' }, + scanner: { numberOfChildren: 10, minChunkSize: 5000, scanKey: '' }, + threads: 10, +}; diff --git a/src/block-range/block-range.mapper.ts b/src/block-range/block-range.mapper.ts new file mode 100644 index 0000000..af12b86 --- /dev/null +++ b/src/block-range/block-range.mapper.ts @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { Serialize } from 'eosjs'; +import { BroadcastMessageContentMapper } from '../common/broadcast'; +import { BlockRangeTaskInput } from './block-range.task-input'; + +export class BlockRangeBroadcastMapper + implements BroadcastMessageContentMapper +{ + public toContent(buffer: Buffer): BlockRangeTaskInput { + const sb = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + array: new Uint8Array(buffer), + }); + + const mode = sb.getName(); + const scanKey = sb.getName(); + const traces = sb.getName(); + const deltas = sb.getName(); + + const startBlock: bigint = Buffer.from(sb.getUint8Array(8)).readBigInt64BE(); + const endBlock: bigint = Buffer.from(sb.getUint8Array(8)).readBigInt64BE(); + + return BlockRangeTaskInput.create( + startBlock, + endBlock, + mode, + scanKey, + traces, + deltas + ); + } + public toSource(content: BlockRangeTaskInput): Buffer { + const { + startBlock, + endBlock, + mode, + scanKey, + featuredTraces: traces, + featuredDeltas: deltas, + } = content; + + const sb = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + }); + + sb.pushName(mode); + sb.pushName(scanKey); + sb.pushName(JSON.stringify(traces)); + sb.pushName(JSON.stringify(deltas)); + + const startBlockBuffer = Buffer.alloc(8); + startBlockBuffer.writeBigInt64BE(startBlock); + const endBlockBuffer = Buffer.alloc(8); + endBlockBuffer.writeBigInt64BE(endBlock); + + return Buffer.concat([startBlockBuffer, endBlockBuffer]); + } +} diff --git a/src/block-range/block-range.task-input.ts b/src/block-range/block-range.task-input.ts new file mode 100644 index 0000000..4988208 --- /dev/null +++ b/src/block-range/block-range.task-input.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +import { FeaturedTrace, FeaturedDelta } from './block-range.types'; +import { parseToFeaturedDeltas, parseToFeaturedTraces } from './block-range.utils'; + +export class BlockRangeTaskInput { + public static create( + startBlock: bigint, + endBlock: bigint, + mode: string, + scanKey: string, + featuredTraces: string | FeaturedTrace[], + featuredDeltas: string | FeaturedDelta[] + ) { + return new BlockRangeTaskInput( + mode, + scanKey, + Array.isArray(featuredTraces) + ? featuredTraces + : parseToFeaturedTraces(featuredTraces), + Array.isArray(featuredDeltas) + ? featuredDeltas + : parseToFeaturedDeltas(featuredDeltas), + startBlock, + endBlock + ); + } + + private constructor( + public readonly mode: string, + public readonly scanKey: string, + public readonly featuredTraces: FeaturedTrace[], + public readonly featuredDeltas: FeaturedDelta[], + public readonly startBlock: bigint, + public readonly endBlock: bigint + ) {} +} diff --git a/src/block-range/block-range.task.ts b/src/block-range/block-range.task.ts new file mode 100644 index 0000000..729eb56 --- /dev/null +++ b/src/block-range/block-range.task.ts @@ -0,0 +1,168 @@ +/* eslint-disable @typescript-eslint/require-await */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { log } from '@alien-worlds/api-core'; +import { setupBlockState } from '../common/block-state'; +import { Delta, Trace } from '../common/blockchain/block-content'; +import { ReceivedBlock, setupBlockReader } from '../common/blockchain/block-reader'; +import { setupBroadcast } from '../common/broadcast'; +import { TaskResolver } from '../common/workers/worker-task'; +import { + createProcessorBroadcastOptions, + ProcessorBroadcast, +} from '../processor/processor.broadcast'; +import { TraceProcessorTaskInput } from '../processor/tasks/trace-processor.task-input'; +import { DeltaProcessorTaskInput } from '../processor/tasks/delta-processor.task-input'; +import { BlockRangeTaskInput } from './block-range.task-input'; +import { FeaturedTrace, FeaturedDelta } from './block-range.types'; +import { extractAllocationFromDeltaRowData } from './block-range.utils'; +import { BlockRangeConfig } from './block-range.config'; + +/** + * + * @param trace + * @param broadcast + * @param featuredTraces + */ +export const handleTrace = async ( + broadcast: ProcessorBroadcast, + featuredTraces: FeaturedTrace[], + trace: Trace, + blockNumber: bigint, + blockTimestamp: Date +) => { + const { id, actionTraces, type } = trace; + + const isFeaturedType = featuredTraces.findIndex(trace => trace.type === type) > -1; + + if (isFeaturedType) { + for (const actionTrace of actionTraces) { + const { + act: { account, name }, + } = actionTrace; + const isFeaturedActionTrace = + featuredTraces.findIndex( + trace => + (trace.contracts.has(account) || trace.contracts.has('*')) && + (trace.actions.has(name) || trace.actions.has('*')) + ) > -1; + + if (isFeaturedActionTrace) { + await broadcast + .sendMessage( + TraceProcessorTaskInput.fromBlockchainData( + id, + actionTrace, + blockNumber, + blockTimestamp + ) + ) + .catch((error: Error) => + log(`Could not send processor task due to: ${error.message}`) + ); + } + } + } +}; + +/** + * + * @param delta + * @param broadcast + * @param featuredDeltas + */ +export const handleDelta = async ( + broadcast: ProcessorBroadcast, + featuredDeltas: FeaturedDelta[], + delta: Delta, + blockNumber: bigint, + blockTimestamp: Date +) => { + const featuredDelta = featuredDeltas.find( + item => item.type === delta.type && item.name === delta.name + ); + + if (featuredDelta) { + const { codes, scopes, tables } = featuredDelta; + for (const row of delta.rows) { + const { code, scope, table } = extractAllocationFromDeltaRowData(row.data); + + if ( + (codes.has('*') || codes.has(code)) && + (scopes.has('*') || scopes.has(scope)) && + (tables.has('*') || tables.has(table)) + ) { + await broadcast + .sendMessage( + DeltaProcessorTaskInput.fromBlockchainData( + delta.name, + code, + scope, + table, + blockNumber, + blockTimestamp, + row + ) + ) + .catch((error: Error) => + log(`Could not send processor task due to: ${error.message}`) + ); + } + } + } +}; + +type SharedData = { config: BlockRangeConfig }; + +export const run = async (input: BlockRangeTaskInput, sharedData: SharedData) => { + const { + startBlock, + endBlock, + featuredTraces, + featuredDeltas, + scanKey, + } = input; + const { config } = sharedData; + const { + reader, + mongo, + broadcast: { url }, + } = config; + const { shouldFetchDeltas, shouldFetchTraces } = reader; + const blockReader = await setupBlockReader(reader); + const blockState = await setupBlockState(mongo); + const broadcastOptions = createProcessorBroadcastOptions(); + const broadcast = await setupBroadcast(url, broadcastOptions); + const traceBroadcast = broadcast as ProcessorBroadcast; + const deltaBroadcast = broadcast as ProcessorBroadcast; + + blockReader.onReceivedBlock((receivedBlock: ReceivedBlock) => { + const { + traces, + deltas, + block: { timestamp }, + thisBlock: { blockNumber }, + } = receivedBlock; + + blockState.updateCurrentBlockNumber(blockNumber).catch(error => log(error)); + traces.forEach(trace => { + handleTrace(traceBroadcast, featuredTraces, trace, blockNumber, timestamp).catch( + error => log(`Trace not handled`, error) + ); + }); + deltas.forEach(delta => { + handleDelta(deltaBroadcast, featuredDeltas, delta, blockNumber, timestamp).catch( + error => log(`Delta not handled`, error) + ); + }); + }); + + blockReader.onError(error => { + TaskResolver.reject(error); + }); + + blockReader.onComplete(() => { + TaskResolver.resolve({ startBlock, endBlock, scanKey }); + }); + + blockReader.readBlocks(startBlock, endBlock, { shouldFetchDeltas, shouldFetchTraces }); +}; diff --git a/src/block-range/block-range.types.ts b/src/block-range/block-range.types.ts new file mode 100644 index 0000000..bd7e633 --- /dev/null +++ b/src/block-range/block-range.types.ts @@ -0,0 +1,20 @@ +export type FeaturedDelta = { + type: string; + name: string; + codes: Set; + scopes: Set; + tables: Set; +}; + +export type FeaturedTrace = { + type: string; + actionTracesVersion: string; + contracts: Set; + actions: Set; +}; + +export type BlockRangeWorkerMessageContent = { + startBlock: bigint; + endBlock: bigint; + scanKey: string; +}; diff --git a/src/block-range/block-range.utils.ts b/src/block-range/block-range.utils.ts new file mode 100644 index 0000000..7b47286 --- /dev/null +++ b/src/block-range/block-range.utils.ts @@ -0,0 +1,100 @@ +import { Serialize } from 'eosjs'; +import { FeaturedTrace, FeaturedDelta } from './block-range.types'; + +export const extractAllocationFromDeltaRowData = (value: Uint8Array) => { + const sb = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + array: value, + }); + + sb.get(); // ? + const code = sb.getName(); + const scope = sb.getName(); + const table = sb.getName(); + + return { code, scope, table }; +}; + +export const extractValues = (value: string): Set => { + const result = new Set(); + if (!value || value.includes('*')) { + result.add('*'); + } else { + value.split(',').forEach(entry => { + if (/^(\*|[A-Za-z0-9_]*)$/g.test(entry)) { + result.add(entry); + } + }); + } + return result; +}; + +// transaction_trace_v0:action_trace_v0#contract:action&contract2:* +// transaction_trace_v0,transaction_trace_v1:action_trace_v0,action_trace_v1#contract:action&contract2:*|action_trace_v1#contract:action1,action2 + +export const parseToFeaturedTraces = (value: string): FeaturedTrace[] => { + const set = new Set(); + + value + .replace(/\s/g, '') + .split('|') + .forEach(valueStr => { + const [versionAndActionTracesVersionStr, contractsStr] = valueStr.split('#'); + const [version, actionTracesVersion] = versionAndActionTracesVersionStr.split(':'); + + contractsStr.split('&').forEach(contractsAndActionsStr => { + const [contractsStr, actionsStr] = contractsAndActionsStr.split(':'); + + const contracts = new Set(); + const actions = new Set(); + extractValues(contractsStr).forEach(contract => contracts.add(contract)); + extractValues(actionsStr).forEach(action => actions.add(action)); + + set.add({ + type: version, + actionTracesVersion, + contracts, + actions, + }); + }); + }); + + return Array.from(set.values()); +}; + +// table_delta_v0:contract_rows#code:scope:table&code:scope2:*|table_delta_v0:other_contract_rows#code:* + +export const parseToFeaturedDeltas = (value: string): FeaturedDelta[] => { + const set = new Set(); + + value + .replace(/\s/g, '') + .split('|') + .forEach(typeStr => { + const [typeAndNameStr, contractStr] = typeStr.split('#'); + const [type, name] = typeAndNameStr.split(':'); + + contractStr.split('&').forEach(contract => { + const [codeStr, scopeStr, tableStr] = contract.split(':'); + + const codes = new Set(); + const scopes = new Set(); + const tables = new Set(); + + extractValues(codeStr).forEach(code => codes.add(code)); + extractValues(scopeStr).forEach(scope => scopes.add(scope)); + extractValues(tableStr).forEach(table => tables.add(table)); + + set.add({ + type, + name, + codes: new Set(), + scopes: new Set(), + tables: new Set(), + }); + }); + }); + + return Array.from(set.values()); +}; diff --git a/src/block-range/index.ts b/src/block-range/index.ts new file mode 100644 index 0000000..867f462 --- /dev/null +++ b/src/block-range/index.ts @@ -0,0 +1 @@ +export * from './start-block-range'; \ No newline at end of file diff --git a/src/block-range/start-block-range.ts b/src/block-range/start-block-range.ts new file mode 100644 index 0000000..acfc72c --- /dev/null +++ b/src/block-range/start-block-range.ts @@ -0,0 +1,145 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-misused-promises */ +import { BlockRangeScanner, setupBlockRangeScanner } from '../common/block-range-scanner'; +import { + BroadcastMessage, + BroadcastMessageContentMapper, + setupBroadcast, +} from '../common/broadcast'; +import { Mode } from '../common/enums'; +import { WorkerMessage } from '../common/workers/worker-message'; +import { WorkerPool } from '../common/workers/worker-pool'; +import { createBlockRangeBroadcastOptions } from './block-range.broadcast'; +import { BlockRangeConfig } from './block-range.config'; +import { BlockRangeTaskInput } from './block-range.task-input'; +import { BlockRangeWorkerMessageContent } from './block-range.types'; + +const blockRangeTaskPath = './block-range.task'; + +export const handleWorkerMessage = async ( + message: WorkerMessage, + workerPool: WorkerPool, + scanner: BlockRangeScanner +) => { + const { + content: { scanKey }, + pid, + } = message; + + await workerPool.releaseWorker(pid); + + if ( + (await scanner.hasUnscannedBlocks(scanKey)) && + message.isTaskResolved() && + workerPool.hasAvailableWorker() + ) { + const scan = await scanner.getNextScanNode(scanKey); + if (scan) { + const { start, end } = scan; + const worker = workerPool.getWorker(); + worker.run( + BlockRangeTaskInput.create( + start, + end, + mode, + scanKey, + featuredTraces, + featuredDeltas + ) + ); + } + } +}; + +/** + * + * @param input + * @param config + */ +export const startMultiWorkerMode = async ( + input: BlockRangeTaskInput, + config: BlockRangeConfig +) => { + const { threads, mongo, scanner: scannerConfig } = config; + const { featuredTraces, featuredDeltas, mode } = input; + const workerPool = new WorkerPool({ + threadsCount: threads, + globalWorkerPath: blockRangeTaskPath, + sharedData: { config }, + }); + const scanner = await setupBlockRangeScanner(mongo, scannerConfig); + + while ( + (await scanner.hasUnscannedBlocks(input.scanKey)) && + workerPool.hasAvailableWorker() + ) { + const { start, end, scanKey } = await scanner.getNextScanNode(input.scanKey); + const worker = workerPool.getWorker(); + worker.onMessage((message: WorkerMessage) => + handleWorkerMessage(message, workerPool, scanner) + ); + worker.run( + BlockRangeTaskInput.create( + start, + end, + mode, + scanKey, + featuredTraces, + featuredDeltas + ) + ); + } +}; + +/** + * + * @param {BlockRangeTaskInput} input + */ +export const startSingleWorkerMode = ( + input: BlockRangeTaskInput, + config: BlockRangeConfig +) => { + const workerPool = new WorkerPool({ + threadsCount: 1, + globalWorkerPath: blockRangeTaskPath, + sharedData: { config }, + }); + const worker = workerPool.getWorker(); + worker.run(input); +}; + +export const handleBlockRangeMessage = async ( + message: BroadcastMessage, + config: BlockRangeConfig +) => { + const { content } = message; + + if (content.mode === Mode.replay) { + await startMultiWorkerMode(content, config); + } else { + startSingleWorkerMode(content, config); + } +}; + +/** + * + * @param broadcastMessageMapper + * @param config + * @returns + */ +export const startBlockRange = async ( + config: BlockRangeConfig, + mapper?: BroadcastMessageContentMapper +) => { + const { + broadcast: { url }, + } = config; + const blockRangeBroadcastOptions = createBlockRangeBroadcastOptions(mapper); + const broadcast = await setupBroadcast(url, blockRangeBroadcastOptions); + + broadcast.onMessage( + blockRangeBroadcastOptions.queues[0].name, + (message: BroadcastMessage) => + handleBlockRangeMessage(message, config) + ); +}; diff --git a/src/common/block-range-scanner/__tests__/block-range-scan.mongo.source.unit.test.ts b/src/common/block-range-scanner/__tests__/block-range-scan.mongo.source.unit.test.ts new file mode 100644 index 0000000..e0ed37a --- /dev/null +++ b/src/common/block-range-scanner/__tests__/block-range-scan.mongo.source.unit.test.ts @@ -0,0 +1,279 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { MongoSource } from '@alien-worlds/api-core'; +import { Long } from 'mongodb'; +import { BlockRangeScanMongoSource } from '../block-range-scan.mongo.source'; +import { BlockNumberOutOfRangeError } from '../block-range-scanner.errors'; + +const db = { + databaseName: 'TestDB', + collection: jest.fn(() => ({ + find: jest.fn(), + findOne: jest.fn(), + updateOne: jest.fn(), + insertOne: jest.fn(), + insertMany: jest.fn(), + deleteOne: jest.fn(), + deleteMany: jest.fn(), + countDocuments: jest.fn(), + findOneAndUpdate: jest.fn(), + })) as any, +}; + +const dto = { + _id: { start: 0n, end: 10n, scan_key: 'test' }, + tree_depth: 0, + processed_block: 0n, + time_stamp: new Date('2022-07-01T09:59:29.035Z'), + is_leaf_node: true, + parent_id: { start: 0n, end: 1n, scan_key: 'test' }, +}; + +const mongoSource = new MongoSource(db as any); + +describe('Block Range scan mongo source Unit tests', () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date('2022-06-24T19:01:30.911Z')); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('"setCurrentBlockProgress" should throw "scanning already completed" error if dto doesn\'t have is_leaf_node prop', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + + dto.is_leaf_node = undefined; + + await (source as any).setCurrentBlockProgress(dto, 2n).catch(error => { + expect(error.message).toEqual( + `(0-10) range has already completed scanning the blockchain.` + ); + }); + + dto.is_leaf_node = true; + }); + + it('"setCurrentBlockProgress" should find completed parent node when processed block number is penultimate in the range', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + + const findCompletedParentNodeMock = jest + .spyOn(source, 'findCompletedParentNode') + .mockImplementation(); + + await (source as any).setCurrentBlockProgress(dto, 9n); + expect(findCompletedParentNodeMock).toBeCalledWith(dto); + }); + + it('"setCurrentBlockProgress" should set processed_block and time_stamp in the range node', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + + await (source as any).setCurrentBlockProgress(dto, 2n); + + const { _id } = dto; + expect((source as any).collection.updateOne).toBeCalledWith( + { _id }, + { + $set: { + processed_block: Long.fromBigInt(2n), + time_stamp: new Date(), + }, + } + ); + }); + + it('"startNextScan" should find unscanned or unfinished leaf node, update its time_stamp and return that document', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + (source as any).collection.findOneAndUpdate.mockImplementation(() => ({ + value: dto, + })); + await (source as any).startNextScan('test'); + + expect((source as any).collection.findOneAndUpdate).toBeCalledWith( + { + $and: [ + { is_leaf_node: true }, + { + $or: [ + { time_stamp: { $exists: false } }, + { time_stamp: { $lt: new Date(Date.now() - 1000) } }, + ], + }, + { '_id.scan_key': 'test' }, + ], + }, + { $set: { time_stamp: new Date() } }, + { + sort: { time_stamp: 1 }, + returnDocument: 'after', + } + ); + }); + + it('"removeAll" should remove all nodes with the provided scan_key', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + await (source as any).removeAll('test'); + + expect((source as any).collection.deleteMany).toBeCalledWith({ + '_id.scan_key': 'test', + }); + }); + + it('"countScanNodes" should count all nodes with the provided scan_key', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + await (source as any).countScanNodes('test'); + + expect((source as any).collection.countDocuments).toBeCalledWith({ + $and: [{ '_id.scan_key': 'test' }], + }); + }); + + it('"countScanNodes" should count all nodes with the provided scan_key within range', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + await (source as any).countScanNodes('test', 1n, 1n); + const options = [ + { '_id.scan_key': 'test' }, + { '_id.start': { $gte: Long.fromBigInt(1n) } }, + { '_id.end': { $lte: Long.fromBigInt(1n) } }, + ]; + expect((source as any).collection.countDocuments).toBeCalledWith({ + $and: options, + }); + }); + + it('"hasScanKey" should return true when a matching document is found', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + (source as any).collection.findOne.mockImplementation(() => dto); + + await (source as any).hasScanKey('test'); + + expect((source as any).collection.findOne).toBeCalledWith({ + $and: [{ '_id.scan_key': 'test' }], + }); + }); + + it('"hasScanKey" should return false when no matching document was found', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + (source as any).collection.findOne.mockImplementation(() => null); + const options = [ + { '_id.scan_key': 'test' }, + { '_id.start': Long.fromBigInt(1n) }, + { '_id.end': Long.fromBigInt(1n) }, + ]; + await (source as any).hasScanKey('test', 1n, 1n); + + expect((source as any).collection.findOne).toBeCalledWith({ + $and: options, + }); + }); + + it('"hasUnscannedNodes" should return true when a matching document is found', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + (source as any).collection.findOne.mockImplementation(() => dto); + + await (source as any).hasUnscannedNodes('test'); + + expect((source as any).collection.findOne).toBeCalledWith({ + $and: [ + { '_id.scan_key': 'test' }, + { tree_depth: { $gt: 0 } }, + { is_leaf_node: true }, + ], + }); + }); + + it('"hasUnscannedNodes" should return false when no matching document was found', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + (source as any).collection.findOne.mockImplementation(() => null); + const options = [ + { '_id.scan_key': 'test' }, + { tree_depth: { $gt: 0 } }, + { is_leaf_node: true }, + { 'parent_id.start': Long.fromBigInt(1n) }, + { 'parent_id.end': Long.fromBigInt(1n) }, + ]; + await (source as any).hasUnscannedNodes('test', 1n, 1n); + + expect((source as any).collection.findOne).toBeCalledWith({ + $and: options, + }); + }); + + it('"findRangeForBlockNumber" should return a range node that matches the key and contains the given block number in the range', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + (source as any).collection.find.mockImplementation(() => ({ + next: () => dto, + })); + + await (source as any).findRangeForBlockNumber(0n, 'test'); + + expect((source as any).collection.find).toBeCalledWith( + { + '_id.start': { $lte: Long.fromBigInt(0n) }, + '_id.end': { $gt: Long.fromBigInt(0n) }, + '_id.scan_key': 'test', + }, + { sort: { tree_depth: -1 } } + ); + }); + + it('"findRangeForBlockNumber" should not do anything when given document has no parent_id', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + + (source as any).collection.find.mockImplementation(() => ({ + next: () => dto, + })); + + await (source as any).findCompletedParentNode({}); + + expect((source as any).collection.deleteOne).not.toBeCalled(); + expect((source as any).collection.find).not.toBeCalled(); + }); + + it('"findRangeForBlockNumber" should remove given document when it contains parent_id and fetch its child nodes', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + + (source as any).collection.find.mockImplementation(async () => ({ + count: () => 1, + })); + + await (source as any).findCompletedParentNode(dto); + + expect((source as any).collection.deleteOne).toBeCalledWith({ + _id: dto._id, + }); + expect((source as any).collection.countDocuments).toBeCalledWith({ + parent_id: dto.parent_id, + }); + }); + + it('"updateProcessedBlockNumber" should call findCompletedParentNode on parent node if no child nodes were found', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + const findRangeForBlockNumberMock = jest + .spyOn(source as any, 'findRangeForBlockNumber') + .mockImplementation(() => ({})); + const setCurrentBlockProgressMock = jest + .spyOn(source as any, 'setCurrentBlockProgress') + .mockImplementation(); + + await source.updateProcessedBlockNumber('test', 0n); + + expect(findRangeForBlockNumberMock).toBeCalled(); + expect(setCurrentBlockProgressMock).toBeCalled(); + }); + + it('"updateProcessedBlockNumber" should throw an error when no block range was found', async () => { + const source = new BlockRangeScanMongoSource(mongoSource); + const findRangeForBlockNumberMock = jest + .spyOn(source as any, 'findRangeForBlockNumber') + .mockImplementation(() => null); + + await source.updateProcessedBlockNumber('test', 0n).catch(error => { + expect(error).toBeInstanceOf(BlockNumberOutOfRangeError); + }); + + findRangeForBlockNumberMock.mockClear(); + }); +}); diff --git a/src/common/block-range-scanner/__tests__/block-range-scan.repository.unit.test.ts b/src/common/block-range-scanner/__tests__/block-range-scan.repository.unit.test.ts new file mode 100644 index 0000000..c16a574 --- /dev/null +++ b/src/common/block-range-scanner/__tests__/block-range-scan.repository.unit.test.ts @@ -0,0 +1,207 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Failure, MongoSource } from '@alien-worlds/api-core'; +import { BlockRangeScan } from '../block-range-scan'; +import { BlockRangeScanMongoSource } from '../block-range-scan.mongo.source'; +import { BlockRangeScanRepository } from '../block-range-scan.repository'; + +const blockRangeScanConfig = { + numberOfChildren: 0, + minChunkSize: 0, + scanKey: 'test', +}; + +const db = { + databaseName: 'TestDB', + collection: jest.fn(() => ({ + find: jest.fn(), + findOne: jest.fn(), + updateOne: jest.fn(), + insertOne: jest.fn(), + insertMany: jest.fn(), + deleteOne: jest.fn(), + deleteMany: jest.fn(), + countDocuments: jest.fn(), + findOneAndUpdate: jest.fn(), + })) as any, +}; + +const dto = { + _id: { start: 0n, end: 10n, scan_key: 'test' }, + tree_depth: 0, + processed_block: 0n, + time_stamp: new Date('2022-07-01T09:59:29.035Z'), + is_leaf_node: true, + parent_id: { start: 0n, end: 1n, scan_key: 'test' }, +}; + +const mongoSource = new MongoSource(db as any); +const blockRangeScanMongoSource = new BlockRangeScanMongoSource(mongoSource); + +describe('Block Range scan repository Unit tests', () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date('2022-06-24T19:01:30.911Z')); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('"startNextScan" should return a result with BlockRangeScan object', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const startNextScanMock = jest + .spyOn(blockRangeScanMongoSource, 'startNextScan') + .mockImplementation(() => dto as any); + + const result = await repo.startNextScan('test'); + + expect(result).toBeInstanceOf(BlockRangeScan); + + startNextScanMock.mockClear(); + }); + + it('"startNextScan" should return null when no block range node was found for the given key', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const startNextScanMock = jest + .spyOn(blockRangeScanMongoSource, 'startNextScan') + .mockImplementation(() => { + throw new Error(); + }); + const result = await repo.startNextScan('test'); + + expect(result).toBeNull(); + + startNextScanMock.mockClear(); + }); + + it('"createScanNodes" should insert multiple scan nodes to the source', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const createChildRangesMock = jest.fn().mockImplementation(() => []); + + BlockRangeScan.createChildRanges = createChildRangesMock; + const insertManyMock = jest + .spyOn(blockRangeScanMongoSource, 'insertMany') + .mockImplementation(async () => ['foo', 'bar']); + + const result = await repo.createScanNodes('test', 0n, 1n); + + expect(createChildRangesMock).toBeCalled(); + expect(insertManyMock).toBeCalled(); + expect(result).toBeTruthy(); + + insertManyMock.mockClear(); + createChildRangesMock.mockClear(); + }); + + it('"createScanNodes" should return false on any error', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const createMock = jest.fn().mockImplementation(() => { + throw new Error(); + }); + BlockRangeScan.create = createMock; + + const result = await repo.createScanNodes('test', 0n, 1n); + + expect(createMock).toBeCalled(); + expect(result).toBeFalsy(); + + createMock.mockClear(); + }); + + it('"countScanNodes" should result with number of nodes', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const countScanNodesMock = jest + .spyOn(blockRangeScanMongoSource, 'countScanNodes') + .mockImplementation(async () => 6); + + const result = await repo.countScanNodes('test', 0n, 1n); + + expect(countScanNodesMock).toBeCalled(); + expect(result).toEqual(6); + + countScanNodesMock.mockClear(); + }); + + it('"removeAll" should result with number of nodes', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const removeAllMock = jest + .spyOn(blockRangeScanMongoSource, 'removeAll') + .mockImplementation(); + + const result = await repo.removeAll('test'); + + expect(removeAllMock).toBeCalled(); + + removeAllMock.mockClear(); + }); + + it('"hasScanKey" should result with boolean value', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const hasScanKeyMock = jest + .spyOn(blockRangeScanMongoSource, 'hasScanKey') + .mockImplementation(async () => true); + + const result = await repo.hasScanKey('test'); + + expect(hasScanKeyMock).toBeCalled(); + expect(result).toEqual(true); + + hasScanKeyMock.mockClear(); + }); + + it('"hasUnscannedNodes" should result with boolean value', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const hasUnscannedNodesMock = jest + .spyOn(blockRangeScanMongoSource, 'hasUnscannedNodes') + .mockImplementation(async () => true); + + const result = await repo.hasUnscannedNodes('test'); + + expect(hasUnscannedNodesMock).toBeCalled(); + expect(result).toEqual(true); + + hasUnscannedNodesMock.mockClear(); + }); + + it('"updateScannedBlockNumber" should call source.updateProcessedBlockNumber and result with no content', async () => { + const repo = new BlockRangeScanRepository( + blockRangeScanMongoSource, + blockRangeScanConfig + ); + const updateScannedBlockNumberMock = jest + .spyOn(blockRangeScanMongoSource, 'updateProcessedBlockNumber') + .mockImplementation(); + + const result = await repo.updateScannedBlockNumber('test', 0n); + + expect(updateScannedBlockNumberMock).toBeCalled(); + + updateScannedBlockNumberMock.mockClear(); + }); +}); diff --git a/src/common/block-range-scanner/__tests__/block-range-scan.unit.test.ts b/src/common/block-range-scanner/__tests__/block-range-scan.unit.test.ts new file mode 100644 index 0000000..ea27671 --- /dev/null +++ b/src/common/block-range-scanner/__tests__/block-range-scan.unit.test.ts @@ -0,0 +1,213 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Long } from 'mongodb'; +import { BlockRangeScan, BlockRangeScanParent } from '../block-range-scan'; + +const list = [ + { + end: 2n, + isLeafNode: true, + parent: { + end: 5n, + scanKey: 'test', + start: 0n, + }, + scanKey: 'test', + start: 0n, + treeDepth: 2, + }, + { + end: 4n, + isLeafNode: true, + parent: { + end: 5n, + scanKey: 'test', + start: 0n, + }, + scanKey: 'test', + start: 2n, + treeDepth: 2, + }, + { + end: 5n, + isLeafNode: true, + parent: { + end: 5n, + scanKey: 'test', + start: 0n, + }, + scanKey: 'test', + start: 4n, + treeDepth: 2, + }, + { + end: 5n, + + parent: { + end: 10n, + scanKey: 'test', + start: 0n, + }, + scanKey: 'test', + start: 0n, + treeDepth: 1, + }, + { + end: 7n, + isLeafNode: true, + parent: { + end: 10n, + scanKey: 'test', + start: 5n, + }, + scanKey: 'test', + start: 5n, + treeDepth: 2, + }, + { + end: 9n, + isLeafNode: true, + parent: { + end: 10n, + scanKey: 'test', + start: 5n, + }, + scanKey: 'test', + start: 7n, + treeDepth: 2, + }, + { + end: 10n, + isLeafNode: true, + parent: { + end: 10n, + scanKey: 'test', + start: 5n, + }, + scanKey: 'test', + start: 9n, + treeDepth: 2, + }, + { + end: 10n, + parent: { + end: 10n, + scanKey: 'test', + start: 0n, + }, + scanKey: 'test', + start: 5n, + treeDepth: 1, + }, +]; + +const document = { + _id: { + start: Long.fromBigInt(1n), + end: Long.fromBigInt(2n), + scan_key: 'test', + }, + tree_depth: 0, + processed_block: Long.fromBigInt(1n), + time_stamp: new Date('2022-06-24T19:01:30.911Z'), + is_leaf_node: false, + parent_id: { + start: Long.fromBigInt(0n), + end: Long.fromBigInt(10n), + scan_key: 'test', + }, +}; + +describe('Block Range scan entity Unit tests', () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date('2022-06-24T19:01:30.911Z')); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('"create" should create an entity based on data', async () => { + const parent = BlockRangeScanParent.create(0n, 10n, 'test'); + const entity = BlockRangeScan.create( + 1n, + 2n, + 'test', + 0, + parent, + false, + 0n, + new Date('2022-06-24T19:01:30.911Z') + ); + + expect(entity.processedBlock).toEqual(0n); + expect(entity.start).toEqual(1n); + expect(entity.end).toEqual(2n); + expect(entity.isLeafNode).toEqual(false); + expect(entity.timestamp.toISOString()).toEqual('2022-06-24T19:01:30.911Z'); + expect(entity.parent.start).toEqual(0n); + expect(entity.parent.end).toEqual(10n); + expect(entity.parent.scanKey).toEqual('test'); + expect(entity.scanKey).toEqual('test'); + expect(entity.treeDepth).toEqual(0); + }); + + it('"fromDocument" should create an entity based on source document', async () => { + const entity = BlockRangeScan.fromDocument(document); + + expect(entity.processedBlock).toEqual(1n); + expect(entity.start).toEqual(1n); + expect(entity.end).toEqual(2n); + expect(entity.isLeafNode).toEqual(false); + expect(entity.timestamp.toISOString()).toEqual('2022-06-24T19:01:30.911Z'); + expect(entity.parent.start).toEqual(0n); + expect(entity.parent.end).toEqual(10n); + expect(entity.parent.scanKey).toEqual('test'); + expect(entity.scanKey).toEqual('test'); + expect(entity.treeDepth).toEqual(0); + }); + + it('"createChildRanges" should create an entity based on source document', async () => { + const ranges = BlockRangeScan.createChildRanges( + BlockRangeScan.create(0n, 10n, 'test', 0), + 2, + 4 + ); + const jsons = ranges.map(range => range.toJson()); + + expect(jsons).toEqual(list); + }); + + it('"setAsLeafNode" should set isLeafNode prop to true', async () => { + const entity = BlockRangeScan.fromDocument(document); + + expect(entity.isLeafNode).toEqual(false); + entity.setAsLeafNode(); + expect(entity.isLeafNode).toEqual(true); + }); + + it('"toDocument" should create source document based on entity', async () => { + const entity = BlockRangeScan.fromDocument(document); + expect(entity.toDocument()).toEqual(document); + }); + + it('"toJson" should create json object based on entity', async () => { + const entity = BlockRangeScan.fromDocument(document); + expect(entity.toJson()).toEqual({ + processedBlock: 1n, + end: 2n, + isLeafNode: false, + parent: { + end: 10n, + scanKey: 'test', + start: 0n, + }, + scanKey: 'test', + start: 1n, + timestamp: new Date('2022-06-24T19:01:30.911Z'), + treeDepth: 0, + }); + }); +}); diff --git a/src/common/block-range-scanner/block-range-scan.mongo.source.ts b/src/common/block-range-scanner/block-range-scan.mongo.source.ts new file mode 100644 index 0000000..f202a41 --- /dev/null +++ b/src/common/block-range-scanner/block-range-scan.mongo.source.ts @@ -0,0 +1,198 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ + +import { + CollectionMongoSource, + MongoSource, + parseToBigInt, +} from '@alien-worlds/api-core'; +import { Long } from 'mongodb'; +import { BlockRangeScanDocument } from './block-range-scanner.dtos'; +import { BlockNumberOutOfRangeError } from './block-range-scanner.errors'; + +/** + * Block range scan nodes data source from the mongo database + * @class + */ +export class BlockRangeScanMongoSource extends CollectionMongoSource { + public static Token = 'BLOCK_RANGE_SCAN_MONGO_SOURCE'; + + /** + * @constructor + * @param {MongoSource} mongoSource + */ + constructor(mongoSource: MongoSource) { + super(mongoSource, 'block_range_scans'); + } + + private async setCurrentBlockProgress( + document: BlockRangeScanDocument, + processedBlock: bigint + ) { + const { _id, is_leaf_node } = document; + const { start, end } = _id; + + if (!is_leaf_node) { + throw new Error( + `(${start.toString()}-${end.toString()}) range has already completed scanning the blockchain.` + ); + } + + if (processedBlock == parseToBigInt(end) - 1n) { + await this.findCompletedParentNode(document); + } else { + await this.collection.updateOne( + { _id }, + { + $set: { + processed_block: Long.fromBigInt(processedBlock), + time_stamp: new Date(), + }, + } + ); + } + } + + public async startNextScan(scanKey: string): Promise { + const result = await this.collection.findOneAndUpdate( + { + $and: [ + { is_leaf_node: true }, + { + $or: [ + { time_stamp: { $exists: false } }, + /* + The trick to not use the same block range again on another thread/worker + ...Probably this could be handled better. + */ + { time_stamp: { $lt: new Date(Date.now() - 1000) } }, + ], + }, + { '_id.scan_key': scanKey }, + ], + }, + { $set: { time_stamp: new Date() } }, + { + sort: { time_stamp: 1 }, + returnDocument: 'after', + } + ); + + return result.value as BlockRangeScanDocument; + } + + public async countScanNodes( + scanKey: string, + startBlock: bigint, + endBlock: bigint + ): Promise { + const options: unknown[] = [{ '_id.scan_key': scanKey }]; + + if (startBlock) { + options.push({ '_id.start': { $gte: Long.fromBigInt(startBlock) } }); + } + + if (endBlock) { + options.push({ '_id.end': { $lte: Long.fromBigInt(endBlock) } }); + } + + const result = await this.collection.countDocuments({ + $and: options, + }); + + return result; + } + + public async removeAll(scanKey: string) { + await this.collection.deleteMany({ '_id.scan_key': scanKey }); + } + + public async hasScanKey( + scanKey: string, + startBlock?: bigint, + endBlock?: bigint + ): Promise { + const options: unknown[] = [{ '_id.scan_key': scanKey }]; + + if (startBlock) { + options.push({ '_id.start': Long.fromBigInt(startBlock) }); + } + + if (endBlock) { + options.push({ '_id.end': Long.fromBigInt(endBlock) }); + } + const dto = await this.collection.findOne({ $and: options }); + + return !!dto; + } + + public async hasUnscannedNodes( + scanKey: string, + startBlock?: bigint, + endBlock?: bigint + ): Promise { + const options: unknown[] = [ + { '_id.scan_key': scanKey }, + { tree_depth: { $gt: 0 } }, + { is_leaf_node: true }, + ]; + + if (startBlock) { + options.push({ 'parent_id.start': Long.fromBigInt(startBlock) }); + } + + if (endBlock) { + options.push({ 'parent_id.end': Long.fromBigInt(endBlock) }); + } + + const dto = await this.collection.findOne({ $and: options }); + + return !!dto; + } + + public async findRangeForBlockNumber(blockNumber: bigint, scanKey: string) { + const result = this.collection.find( + { + '_id.start': { $lte: Long.fromBigInt(blockNumber) }, + '_id.end': { $gt: Long.fromBigInt(blockNumber) }, + '_id.scan_key': scanKey, + }, + { sort: { tree_depth: -1 } } + ); + const document = await result.next(); + return document; + } + + public async findCompletedParentNode(document: BlockRangeScanDocument) { + const { _id, parent_id } = document; + + if (parent_id) { + await this.collection.deleteOne({ _id }); + // fetch all child nodes with parent id that matches this parent_id + const matchingParentCount = await this.collection.countDocuments({ + parent_id, + }); + if (matchingParentCount == 0) { + const parentDocument: BlockRangeScanDocument = await this.collection.findOne({ + _id: parent_id, + }); + await this.findCompletedParentNode(parentDocument); + } + } + } + + public async updateProcessedBlockNumber( + scanKey: string, + blockNumber: bigint + ): Promise { + const range: BlockRangeScanDocument = await this.findRangeForBlockNumber( + blockNumber, + scanKey + ); + + if (range) { + return this.setCurrentBlockProgress(range, blockNumber); + } + + throw new BlockNumberOutOfRangeError(blockNumber, scanKey); + } +} diff --git a/src/common/block-range-scanner/block-range-scan.repository.ts b/src/common/block-range-scanner/block-range-scan.repository.ts new file mode 100644 index 0000000..10bb196 --- /dev/null +++ b/src/common/block-range-scanner/block-range-scan.repository.ts @@ -0,0 +1,84 @@ +import { BlockRangeScan } from './block-range-scan'; +import { BlockRangeScanMongoSource } from './block-range-scan.mongo.source'; +import { BlockRangeScanConfig } from './block-range-scanner.config'; + +export class BlockRangeScanRepository { + constructor( + private readonly source: BlockRangeScanMongoSource, + private readonly config: BlockRangeScanConfig + ) {} + + public async startNextScan(scanKey: string): Promise { + try { + const document = await this.source.startNextScan(scanKey); + return document ? BlockRangeScan.fromDocument(document) : null; + } catch (error) { + return null; + } + } + + public async createScanNodes( + scanKey: string, + startBlock: bigint, + endBlock: bigint + ): Promise { + try { + const { numberOfChildren, minChunkSize } = this.config; + + const rootRange = BlockRangeScan.create(startBlock, endBlock, scanKey, 0); + const rangesToPersist = [rootRange]; + const childRanges = BlockRangeScan.createChildRanges( + rootRange, + numberOfChildren, + minChunkSize + ); + childRanges.forEach(range => rangesToPersist.push(range)); + + const documents = rangesToPersist.map(range => range.toDocument()); + await this.source.insertMany(documents); + + return true; + } catch (error) { + return false; + } + } + + public async countScanNodes( + scanKey: string, + startBlock: bigint, + endBlock: bigint + ): Promise { + try { + return this.source.countScanNodes(scanKey, startBlock, endBlock); + } catch (error) { + return -1; + } + } + + public async removeAll(scanKey: string): Promise { + await this.source.removeAll(scanKey); + } + + public async hasScanKey( + scanKey: string, + startBlock?: bigint, + endBlock?: bigint + ): Promise { + return this.source.hasScanKey(scanKey, startBlock, endBlock); + } + + public async hasUnscannedNodes( + scanKey: string, + startBlock?: bigint, + endBlock?: bigint + ): Promise { + return this.source.hasUnscannedNodes(scanKey, startBlock, endBlock); + } + + public async updateScannedBlockNumber( + scanKey: string, + blockNumber: bigint + ): Promise { + await this.source.updateProcessedBlockNumber(scanKey, blockNumber); + } +} diff --git a/src/common/block-range-scanner/block-range-scan.ts b/src/common/block-range-scanner/block-range-scan.ts new file mode 100644 index 0000000..28e6ed4 --- /dev/null +++ b/src/common/block-range-scanner/block-range-scan.ts @@ -0,0 +1,235 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { parseToBigInt, removeUndefinedProperties } from '@alien-worlds/api-core'; +import { Long } from 'mongodb'; +import { + BlockRangeScanDocument, + BlockRangeScanIdDocument, +} from './block-range-scanner.dtos'; + +export class BlockRangeScanParent { + /** + * @constructor + * @private + * @param {bigint} start + * @param {bigint} end + * @param {string} scanKey + */ + private constructor( + public readonly start: bigint, + public readonly end: bigint, + public readonly scanKey: string + ) {} + + public static create(start: bigint, end: bigint, scanKey: string) { + return new BlockRangeScanParent(start, end, scanKey); + } + + public static fromDocument(document: BlockRangeScanIdDocument) { + const { start, end, scan_key } = document; + + return new BlockRangeScanParent(parseToBigInt(start), parseToBigInt(end), scan_key); + } + + public toDocument() { + const doc = { + start: Long.fromString(this.start.toString()), + end: Long.fromString(this.end.toString()), + scan_key: this.scanKey, + }; + + return doc; + } + + public toJson() { + const { start, end, scanKey } = this; + + return { start, end, scanKey }; + } +} + +/** + * @class + */ +export class BlockRangeScan { + protected constructor( + public readonly start: bigint, + public readonly end: bigint, + public readonly scanKey: string, + public readonly treeDepth: number, + public readonly parent?: BlockRangeScanParent, + public isLeafNode?: boolean, + public readonly processedBlock?: bigint, + public readonly timestamp?: Date + ) {} + + /** + * Create instance of the BlockRangeScanNode + * + * @static + * @param {bigint | number | string} startBlock + * @param {bigint | number | string} endBlock + * @param {string} scanKey + * @param {number} treeDepth + * @param {BlockRangeScanParent=} parent + * @param {boolean} isLeafNode + * @param {bigint | number | string} processedBlock + * @param {Date} timestamp + * @returns {BlockRangeScan} + */ + public static create( + startBlock: bigint | number | string, + endBlock: bigint | number | string, + scanKey: string, + treeDepth: number, + parent?: BlockRangeScanParent, + isLeafNode?: boolean, + processedBlock?: bigint | number | string, + timestamp?: Date + ): BlockRangeScan { + let currentRangeAsBigInt: bigint; + + if ( + typeof processedBlock === 'bigint' || + typeof processedBlock === 'number' || + typeof processedBlock === 'string' + ) { + currentRangeAsBigInt = parseToBigInt(processedBlock); + } + + return new BlockRangeScan( + parseToBigInt(startBlock), + parseToBigInt(endBlock), + scanKey, + treeDepth, + parent, + isLeafNode, + currentRangeAsBigInt, + timestamp + ); + } + + public static fromDocument(document: BlockRangeScanDocument) { + const { + _id: { start, end, scan_key }, + processed_block, + time_stamp, + tree_depth, + parent_id, + is_leaf_node, + } = document; + + const parent = parent_id ? BlockRangeScanParent.fromDocument(parent_id) : null; + + let processedBlock: bigint; + + if (processed_block) { + processedBlock = parseToBigInt(processed_block); + } + + return new BlockRangeScan( + parseToBigInt(start), + parseToBigInt(end), + scan_key, + tree_depth, + parent, + is_leaf_node, + processedBlock, + time_stamp + ); + } + + public static createChildRanges( + blockRange: BlockRangeScan, + numberOfChildren: number, + maxChunkSize: number + ): BlockRangeScan[] { + const { start: parentStart, end: parentEnd, scanKey, treeDepth } = blockRange; + const chunkSize = (parentEnd - parentStart) / BigInt(numberOfChildren); + const rangesToPersist: BlockRangeScan[] = []; + let start = parentStart; + + while (start < parentEnd) { + const x = start + BigInt(chunkSize); + const end = x < parentEnd ? x : parentEnd; + const range = BlockRangeScan.create( + start, + end, + scanKey, + treeDepth + 1, + BlockRangeScanParent.create(parentStart, parentEnd, scanKey) + ); + + if (end - start > maxChunkSize) { + const childRanges = BlockRangeScan.createChildRanges( + range, + numberOfChildren, + maxChunkSize + ); + childRanges.forEach(range => rangesToPersist.push(range)); + } else { + range.setAsLeafNode(); + } + + rangesToPersist.push(range); + start += chunkSize; + } + + return rangesToPersist; + } + + public setAsLeafNode() { + this.isLeafNode = true; + } + + public toDocument() { + const doc: BlockRangeScanDocument = { + _id: { + start: Long.fromString(this.start.toString()), + end: Long.fromString(this.end.toString()), + scan_key: this.scanKey, + }, + tree_depth: this.treeDepth, + }; + + if (typeof this.processedBlock == 'bigint') { + doc.processed_block = Long.fromString(this.processedBlock.toString()); + } + + if (typeof this.isLeafNode == 'boolean') { + doc.is_leaf_node = this.isLeafNode; + } + + if (this.parent) { + doc.parent_id = this.parent.toDocument(); + } + + if (this.timestamp) { + doc.time_stamp = this.timestamp; + } + + return doc; + } + + public toJson() { + const { start, end, scanKey, isLeafNode, treeDepth, timestamp, processedBlock } = + this; + + const json = { + start, + end, + scanKey, + isLeafNode, + treeDepth, + timestamp, + processedBlock, + parent: null, + }; + + if (this.parent) { + json.parent = this.parent.toJson(); + } + + return removeUndefinedProperties(json); + } +} diff --git a/src/common/block-range-scanner/block-range-scanner.config.ts b/src/common/block-range-scanner/block-range-scanner.config.ts new file mode 100644 index 0000000..3be80fa --- /dev/null +++ b/src/common/block-range-scanner/block-range-scanner.config.ts @@ -0,0 +1,5 @@ +export type BlockRangeScanConfig = { + numberOfChildren: number; + minChunkSize: number; + scanKey: string; +}; diff --git a/src/common/block-range-scanner/block-range-scanner.dtos.ts b/src/common/block-range-scanner/block-range-scanner.dtos.ts new file mode 100644 index 0000000..73aeaf6 --- /dev/null +++ b/src/common/block-range-scanner/block-range-scanner.dtos.ts @@ -0,0 +1,16 @@ +import { Long } from 'mongodb'; + +export type BlockRangeScanIdDocument = { + start: Long; + end: Long; + scan_key: string; +}; + +export type BlockRangeScanDocument = { + _id: BlockRangeScanIdDocument; + tree_depth: number; + processed_block?: Long; + time_stamp?: Date; + is_leaf_node?: boolean; + parent_id?: BlockRangeScanIdDocument; +}; diff --git a/src/common/block-range-scanner/block-range-scanner.errors.ts b/src/common/block-range-scanner/block-range-scanner.errors.ts new file mode 100644 index 0000000..8f68b6c --- /dev/null +++ b/src/common/block-range-scanner/block-range-scanner.errors.ts @@ -0,0 +1,15 @@ +export class BlockNumberOutOfRangeError extends Error { + constructor(public readonly blockNumber: bigint, public readonly scanKey: string) { + super( + `Block number ${blockNumber} is out of range or is assigned to a different key than "${scanKey}"` + ); + } +} + +export class NoBlockRangeFoundError extends Error { + public static Token = 'NO_BLOCK_RANGE_FOUND_ERROR'; + constructor(public readonly scanKey: string) { + super(`No block range was found for the key "${scanKey}"`); + this.name = NoBlockRangeFoundError.Token; + } +} diff --git a/src/common/block-range-scanner/block-range-scanner.ts b/src/common/block-range-scanner/block-range-scanner.ts new file mode 100644 index 0000000..7b6fad5 --- /dev/null +++ b/src/common/block-range-scanner/block-range-scanner.ts @@ -0,0 +1,41 @@ +import { BlockRangeScan } from './block-range-scan'; +import { BlockRangeScanRepository } from './block-range-scan.repository'; + +/** + * @class + */ +export class BlockRangeScanner { + constructor(private blockRangeScanRepository: BlockRangeScanRepository) {} + + public async createScanNodes( + key: string, + startBlock: bigint, + endBlock: bigint + ): Promise { + const hasScanKey = await this.blockRangeScanRepository.hasScanKey( + key, + startBlock, + endBlock + ); + + if (hasScanKey) { + return false; + } + + return this.blockRangeScanRepository.createScanNodes(key, startBlock, endBlock); + } + + public async getNextScanNode(key: string): Promise { + return this.blockRangeScanRepository.startNextScan(key); + } + + public async hasUnscannedBlocks( + key: string, + startBlock?: bigint, + endBlock?: bigint + ): Promise { + return this.blockRangeScanRepository.hasScanKey(key, startBlock, endBlock); + } + + // public onScanComplete(handler: (scan: BlockRangeScan) => Promise): void {} +} diff --git a/src/common/block-range-scanner/block-range-scanner.utils.ts b/src/common/block-range-scanner/block-range-scanner.utils.ts new file mode 100644 index 0000000..f0e114b --- /dev/null +++ b/src/common/block-range-scanner/block-range-scanner.utils.ts @@ -0,0 +1,23 @@ +import { connectMongo, MongoConfig, MongoSource } from '@alien-worlds/api-core'; +import { BlockRangeScanMongoSource } from './block-range-scan.mongo.source'; +import { BlockRangeScanRepository } from './block-range-scan.repository'; +import { BlockRangeScanner } from './block-range-scanner'; +import { BlockRangeScanConfig } from './block-range-scanner.config'; + +export const setupBlockRangeScanner = async ( + mongo: MongoSource | MongoConfig, + config: BlockRangeScanConfig +): Promise => { + let mongoSource: MongoSource; + if (mongo instanceof MongoSource) { + mongoSource = mongo; + } else { + const db = await connectMongo(mongo); + mongoSource = new MongoSource(db); + } + const source = new BlockRangeScanMongoSource(mongoSource); + const repository = new BlockRangeScanRepository(source, config); + const scanner: BlockRangeScanner = new BlockRangeScanner(repository); + + return scanner; +}; diff --git a/src/common/block-range-scanner/index.ts b/src/common/block-range-scanner/index.ts new file mode 100644 index 0000000..6287a62 --- /dev/null +++ b/src/common/block-range-scanner/index.ts @@ -0,0 +1,8 @@ +export * from './block-range-scan.mongo.source'; +export * from './block-range-scan.repository'; +export * from './block-range-scan'; +export * from './block-range-scanner.config'; +export * from './block-range-scanner.dtos'; +export * from './block-range-scanner.errors'; +export * from './block-range-scanner'; +export * from './block-range-scanner.utils'; diff --git a/src/common/block-state/block-state.source.ts b/src/common/block-state/block-state.source.ts new file mode 100644 index 0000000..d8aa6af --- /dev/null +++ b/src/common/block-state/block-state.source.ts @@ -0,0 +1,33 @@ +import { + CollectionMongoSource, + Long, + MongoSource, + parseToBigInt, +} from '@alien-worlds/api-core'; + +export type BlockStateDocument = { + name: string; + value: Long; +}; + +export class BlockStateSource extends CollectionMongoSource { + constructor(mongoSource: MongoSource) { + super(mongoSource, 'history_tools_state'); + } + + public async updateCurrentBlockNumber(value: bigint): Promise { + const { modifiedCount, upsertedCount } = await this.update( + { name: 'current_block' }, + { $max: { value: Long.fromBigInt(value) } }, + { upsert: true } + ); + + return modifiedCount > 0 || upsertedCount > 0; + } + + public async getCurrentBlockNumber(): Promise { + const currentBlock = await this.findOne({ name: 'current_block' }); + + return parseToBigInt(currentBlock ? currentBlock.value : Long.NEG_ONE); + } +} diff --git a/src/common/block-state/block-state.ts b/src/common/block-state/block-state.ts new file mode 100644 index 0000000..b4cfe6c --- /dev/null +++ b/src/common/block-state/block-state.ts @@ -0,0 +1,17 @@ +import { MongoSource } from '@alien-worlds/api-core'; +import { BlockStateSource } from './block-state.source'; + +export class BlockState { + private source: BlockStateSource; + + constructor(mongo: MongoSource) { + this.source = new BlockStateSource(mongo); + } + + public async updateCurrentBlockNumber(value: bigint): Promise { + return this.source.updateCurrentBlockNumber(value); + } + public async getCurrentBlockNumber(): Promise { + return this.source.getCurrentBlockNumber(); + } +} diff --git a/src/common/block-state/block-state.utils.ts b/src/common/block-state/block-state.utils.ts new file mode 100644 index 0000000..0b631e2 --- /dev/null +++ b/src/common/block-state/block-state.utils.ts @@ -0,0 +1,12 @@ +import { connectMongo, MongoConfig, MongoSource } from '@alien-worlds/api-core'; +import { BlockState } from './block-state'; + +export const setupBlockState = async (mongo: MongoSource | MongoConfig) => { + if (mongo instanceof MongoSource) { + return new BlockState(mongo); + } + + const db = await connectMongo(mongo); + + return new BlockState(new MongoSource(db)); +}; diff --git a/src/common/block-state/index.ts b/src/common/block-state/index.ts new file mode 100644 index 0000000..953d660 --- /dev/null +++ b/src/common/block-state/index.ts @@ -0,0 +1,3 @@ +export * from './block-state'; +export * from './block-state.source'; +export * from './block-state.utils'; diff --git a/src/common/blockchain/block-content/abi/__tests__/abi.unit.test.ts b/src/common/blockchain/block-content/abi/__tests__/abi.unit.test.ts new file mode 100644 index 0000000..0e6f44c --- /dev/null +++ b/src/common/blockchain/block-content/abi/__tests__/abi.unit.test.ts @@ -0,0 +1,185 @@ +import { Abi } from '../abi'; +import { AbiExtension } from '../abi-extension'; +import { AbiStruct, StructField } from '../abi-struct'; +import { AbiTable } from '../abi-table'; +import { AbiType } from '../abi-type'; +import { AbiVariant } from '../abi-variant'; +import { AbiErrorMessage } from '../abi-error-message'; +import { RicardianClause } from '../ricardian-clause'; + +jest.mock('eosjs/dist/eosjs-serialize'); + +const typeDto = { + new_type_name: 'some_name', + type: 'some_type', +}; + +const variantDto = { + name: 'SOME_NAME', + types: ['TYPE_1', 'TYPE_2'], +}; + +const customTypeDto = { + new_type_name: 'SOME_TYPE_NAME', + type: 'SOME_TYPE', +}; + +const structDto = { + name: 'foo.name', + base: 'FOO', + fields: [{ name: 'FIELD_NAME', type: 'FIELD_TYPE' }], +}; + +const structFieldDto = { + name: 'FIELD_NAME', + type: 'FIELD_TYPE', +}; + +const ricardianClauseDto = { + id: 'SOME_ID', + body: 'SOME_BODY', +}; + +const abiExtensionDto = { + tag: 12345, + value: 'SOME_BODY', +}; + +const errorMessageDto = { + error_code: 200, + error_msg: 'SOME_MESSAGE', +}; + +const tableDto = { + name: 'accounts', + type: 'account', + index_type: 'i64', + key_names: ['KEY_NAME_1'], + key_types: ['KEY_TYPE_1'], +}; + +const actionDto = { + name: 'create', + type: 'create', + ricardian_contract: 'contract', +}; + +const abiDto = { + version: 'version_1', + types: [typeDto], + structs: [structDto], + tables: [tableDto], + actions: [actionDto], + ricardian_clauses: [ricardianClauseDto], + abi_extensions: [abiExtensionDto], + error_messages: [errorMessageDto], + variants: [variantDto], +}; + +describe('CustomType Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = AbiType.fromDto(customTypeDto); + expect(entity.toDto()).toEqual(customTypeDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = AbiType.fromDto(customTypeDto); + expect(entity.toDto()).toEqual(customTypeDto); + }); +}); + +describe('Struct Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = AbiStruct.fromDto(structDto); + expect(entity.toDto()).toEqual(structDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = AbiStruct.fromDto(structDto); + expect(entity.toDto()).toEqual(structDto); + }); +}); + +describe('StructField Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = StructField.fromDto(structFieldDto); + expect(entity.toDto()).toEqual(structFieldDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = StructField.fromDto(structFieldDto); + expect(entity.toDto()).toEqual(structFieldDto); + }); +}); + +describe('RicardianClause Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = RicardianClause.fromDto(ricardianClauseDto); + expect(entity.toDto()).toEqual(ricardianClauseDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = RicardianClause.fromDto(ricardianClauseDto); + expect(entity.toDto()).toEqual(ricardianClauseDto); + }); +}); + +describe('AbiExtension Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = AbiExtension.fromDto(abiExtensionDto); + expect(entity.toDto()).toEqual(abiExtensionDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = AbiExtension.fromDto(abiExtensionDto); + expect(entity.toDto()).toEqual(abiExtensionDto); + }); +}); + +describe('AbiErrorMessage Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = AbiErrorMessage.fromDto(errorMessageDto); + expect(entity.toDto()).toEqual(errorMessageDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = AbiErrorMessage.fromDto(errorMessageDto); + expect(entity.toDto()).toEqual(errorMessageDto); + }); +}); + +describe('Variant Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = AbiVariant.fromDto(variantDto); + expect(entity.toDto()).toEqual(variantDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = AbiVariant.fromDto(variantDto); + expect(entity.toDto()).toEqual(variantDto); + }); +}); + +describe('Table Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = AbiTable.fromDto(tableDto); + expect(entity.toDto()).toEqual(tableDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = AbiTable.fromDto(tableDto); + expect(entity.toDto()).toEqual(tableDto); + }); +}); + +describe('Abi Unit tests', () => { + it('"fromDto" should create entity', () => { + const entity = Abi.fromDto(abiDto); + expect(entity.toDto()).toEqual(abiDto); + }); + + it('"toDto" should create dto from entity', () => { + const entity = Abi.fromDto(abiDto); + expect(entity.toDto()).toEqual(abiDto); + }); +}); diff --git a/src/common/blockchain/block-content/abi/abi-action.ts b/src/common/blockchain/block-content/abi/abi-action.ts new file mode 100644 index 0000000..97d4047 --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi-action.ts @@ -0,0 +1,40 @@ +import { AbiActionDto } from './abi.dtos'; + +/** + * @class + */ +export class AbiAction { + /** + * + * @param {string} name - The name of the action as defined in the contract + * @param {string} type - The name of the implicit struct as described in the ABI + * @param {string=} ricardianContract - An optional ricardian clause to associate to this action describing its intended functionality. + */ + private constructor( + public readonly name: string, + public readonly type: string, + public readonly ricardianContract?: string + ) {} + + /** + * @returns {AbiActionDto} + */ + public toDto(): AbiActionDto { + const { name, type, ricardianContract } = this; + return { + name, + type, + ricardian_contract: ricardianContract, + }; + } + + /** + * @static + * @param {AbiActionDto} dto + * @returns {AbiAction} + */ + public static fromDto(dto: AbiActionDto): AbiAction { + const { name, type, ricardian_contract } = dto; + return new AbiAction(name, type, ricardian_contract); + } +} diff --git a/src/common/blockchain/block-content/abi/abi-error-message.ts b/src/common/blockchain/block-content/abi/abi-error-message.ts new file mode 100644 index 0000000..f28147a --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi-error-message.ts @@ -0,0 +1,34 @@ +import { AbiErrorMessageDto } from './abi.dtos'; + +/** + * @class + */ +export class AbiErrorMessage { + /** + * + * @param {number} errorCode + * @param {string} message + */ + private constructor( + public readonly errorCode: number, + public readonly message: string + ) {} + + /** + * @returns {AbiErrorMessageDto} + */ + public toDto(): AbiErrorMessageDto { + const { errorCode, message } = this; + return { error_code: errorCode, error_msg: message }; + } + + /** + * @static + * @param {AbiErrorMessageDto} dto + * @returns {AbiErrorMessage} + */ + public static fromDto(dto: AbiErrorMessageDto): AbiErrorMessage { + const { error_code, error_msg } = dto; + return new AbiErrorMessage(error_code, error_msg); + } +} diff --git a/src/common/blockchain/block-content/abi/abi-extension.ts b/src/common/blockchain/block-content/abi/abi-extension.ts new file mode 100644 index 0000000..7b204f0 --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi-extension.ts @@ -0,0 +1,31 @@ +import { AbiExtensionDto } from './abi.dtos'; + +/** + * @class + */ +export class AbiExtension { + /** + * + * @param {number} tag + * @param {string} value + */ + private constructor(public readonly tag: number, public readonly value: string) {} + + /** + * @returns {AbiExtensionDto} + */ + public toDto(): AbiExtensionDto { + const { tag, value } = this; + return { tag, value }; + } + + /** + * @static + * @param {AbiExtensionDto} dto + * @returns {AbiExtension} + */ + public static fromDto(dto: AbiExtensionDto): AbiExtension { + const { tag, value } = dto; + return new AbiExtension(tag, value); + } +} diff --git a/src/common/blockchain/block-content/abi/abi-struct.ts b/src/common/blockchain/block-content/abi/abi-struct.ts new file mode 100644 index 0000000..3bbe6db --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi-struct.ts @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/unbound-method */ +import { AbiStructDto, FieldDto } from './abi.dtos'; + +/** + * @class + */ +export class StructField { + /** + * + * @param {string} name - The field's name + * @param {string} type - The field's type + */ + private constructor(public readonly name: string, public readonly type: string) {} + + /** + * @returns {FieldDto} + */ + public toDto(): FieldDto { + const { name, type } = this; + return { name, type }; + } + + /** + * @static + * @param {FieldDto} dto + * @returns {StructField} + */ + public static fromDto(dto: FieldDto): StructField { + const { name, type } = dto; + return new StructField(name, type); + } +} + +/** + * @class + */ +export class AbiStruct { + /** + * + * @param {string} name + * @param {string} base - Inheritance, parent struct + * @param {StructField[]} fields - Array of field objects describing the struct's fields + */ + private constructor( + public readonly name: string, + public readonly base: string, + public readonly fields: StructField[] + ) {} + + /** + * @returns {AbiStructDto} + */ + public toDto(): AbiStructDto { + return { + name: this.name, + base: this.base, + fields: this.fields.map(field => field.toDto()), + }; + } + + /** + * @static + * @param {AbiStructDto} dto + * @returns {AbiStruct} + */ + public static fromDto(dto: AbiStructDto): AbiStruct { + const { name, base, fields } = dto; + return new AbiStruct(name, base, fields.map(StructField.fromDto)); + } +} diff --git a/src/common/blockchain/block-content/abi/abi-table.ts b/src/common/blockchain/block-content/abi/abi-table.ts new file mode 100644 index 0000000..f952a8f --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi-table.ts @@ -0,0 +1,46 @@ +import { AbiTableDto } from './abi.dtos'; + +/** + * @class + */ +export class AbiTable { + /** + * + * @param {string} name - The name of the table, determined during instantiation. + * @param {string} type - The table's corresponding struct + * @param {string} indexType - The type of primary index of this table + * @param {string[]} keyNames - An array of key names, length must equal length of key_types member + * @param {string[]} keyTypes - An array of key types that correspond to key names array member, length of array must equal length of key names array. + */ + private constructor( + public readonly name: string, + public readonly type: string, + public readonly indexType: string, + public readonly keyNames: string[], + public readonly keyTypes: string[] + ) {} + + /** + * @returns {AbiTableDto} + */ + public toDto(): AbiTableDto { + const { name, type, indexType, keyNames, keyTypes } = this; + return { + name, + type, + index_type: indexType, + key_names: keyNames, + key_types: keyTypes, + }; + } + + /** + * @static + * @param {AbiTableDto} dto + * @returns {AbiTable} + */ + public static fromDto(dto: AbiTableDto): AbiTable { + const { name, type, index_type, key_names, key_types } = dto; + return new AbiTable(name, type, index_type, key_names, key_types); + } +} diff --git a/src/common/blockchain/block-content/abi/abi-type.ts b/src/common/blockchain/block-content/abi/abi-type.ts new file mode 100644 index 0000000..0088a02 --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi-type.ts @@ -0,0 +1,40 @@ +import { AbiTypeDto } from './abi.dtos'; + +/** + * Type entity + * @class + */ +export class AbiType { + /** + * + * @param {string} newTypeName + * @param {string} type + */ + private constructor( + public readonly newTypeName: string, + public readonly type: string + ) {} + + /** + * Parse Type entity to DTO + * @returns {AbiTypeDto} + */ + public toDto(): AbiTypeDto { + return { + new_type_name: this.newTypeName, + type: this.type, + }; + } + + /** + * Create ABI entity based on provided DTO + * + * @static + * @param {AbiTypeDto} dto + * @returns {AbiType} + */ + public static fromDto(dto: AbiTypeDto): AbiType { + const { new_type_name, type } = dto; + return new AbiType(new_type_name, type); + } +} diff --git a/src/common/blockchain/block-content/abi/abi-variant.ts b/src/common/blockchain/block-content/abi/abi-variant.ts new file mode 100644 index 0000000..3713fa8 --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi-variant.ts @@ -0,0 +1,31 @@ +import { AbiVariantDto } from './abi.dtos'; + +/** + * @class + */ +export class AbiVariant { + /** + * + * @param {string} name + * @param {string[]} types + */ + private constructor(public readonly name: string, public readonly types: string[]) {} + + /** + * @returns {AbiVariantDto} + */ + public toDto(): AbiVariantDto { + const { name, types } = this; + return { name, types }; + } + + /** + * @static + * @param {AbiVariantDto} dto + * @returns {AbiVariant} + */ + public static fromDto(dto: AbiVariantDto): AbiVariant { + const { name, types } = dto; + return new AbiVariant(name, types); + } +} diff --git a/src/common/blockchain/block-content/abi/abi.dtos.ts b/src/common/blockchain/block-content/abi/abi.dtos.ts new file mode 100644 index 0000000..81bb4ab --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi.dtos.ts @@ -0,0 +1,136 @@ +export type AbiActionDto = { + name: string; // The name of the action as defined in the contract + type: string; // The name of the implicit struct as described in the ABI + ricardian_contract: string; // An optional ricardian clause to associate to this action describing its intended functionality. +}; + +export type AbiDto = { + version: string; + types: AbiTypeDto[]; + structs: AbiStructDto[]; + tables: AbiTableDto[]; + actions: AbiActionDto[]; + ricardian_clauses: RicardianClauseDto[]; + abi_extensions: AbiExtensionDto[]; + error_messages: AbiErrorMessageDto[]; + variants?: AbiVariantDto[]; +}; + +export type AbiErrorMessageDto = { + error_code: number; + error_msg: string; +}; + +export type AbiVariantDto = { + name: string; + types: string[]; +}; + +export type RicardianClauseDto = { + id: string; + body: string; +}; + +export type AbiExtensionDto = { + tag: number; + value: string; +}; + +export type AbiTypeDto = { + new_type_name: string; + type: string; +}; + +export type AbiStructFieldDto = { + name: string; + type: string; +}; + +export type AbiStructDto = { + name: string; + base: string; + fields: AbiStructFieldDto[]; +}; + +export type CreateStructDto = { + name: 'create'; + base: ''; + fields: [IssuerFieldDto, MaximumSupplyFieldDto]; +}; + +export type IssueStructDto = { + name: 'issue'; + base: ''; + fields: [ToFieldDto, QuantityFieldDto, MemoFieldDto]; +}; + +export type RetireStructDto = { + name: 'retire'; + base: ''; + fields: [QuantityFieldDto, MemoFieldDto]; +}; + +export type TransfereStructDto = { + name: 'transfer'; + base: ''; + fields: [FromFieldDto, ToFieldDto, QuantityFieldDto, MemoFieldDto]; +}; + +export type CloseStructDto = { + name: 'close'; + base: ''; + fields: [SymbolFieldDto, OwnerFieldDto]; +}; + +export type FieldDto = { + name: string; + type: string; +}; + +export type OwnerFieldDto = { + name: 'owner'; + type: 'name'; +}; + +export type SymbolFieldDto = { + name: 'symbol'; + type: 'symbol'; +}; + +export type MemoFieldDto = { + name: 'memo'; + type: 'string'; +}; + +export type QuantityFieldDto = { + name: 'quantity'; + type: 'asset'; +}; + +export type ToFieldDto = { + name: 'to'; + type: 'name'; +}; + +export type FromFieldDto = { + name: 'from'; + type: 'name'; +}; + +export type IssuerFieldDto = { + name: 'issuer'; + type: 'name'; +}; + +export type MaximumSupplyFieldDto = { + name: 'maximum_supply'; + type: 'asset'; +}; + +export type AbiTableDto = { + name: string; // 'accounts' | 'stats' + type: string; // 'account' | 'currency_stats' ... Corresponds to previously defined struct + index_type: string; // 'i64' + key_names: string[]; + key_types: string[]; +}; diff --git a/src/common/blockchain/block-content/abi/abi.ts b/src/common/blockchain/block-content/abi/abi.ts new file mode 100644 index 0000000..c7f4af4 --- /dev/null +++ b/src/common/blockchain/block-content/abi/abi.ts @@ -0,0 +1,119 @@ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +import { getTypesFromAbi } from 'eosjs/dist/eosjs-serialize'; + +import { AbiDto } from './abi.dtos'; +import { Serialize } from 'eosjs'; +import { AbiType } from './abi-type'; +import { AbiStruct } from './abi-struct'; +import { AbiTable } from './abi-table'; +import { AbiAction } from './abi-action'; +import { RicardianClause } from './ricardian-clause'; +import { AbiExtension } from './abi-extension'; +import { AbiErrorMessage } from './abi-error-message'; +import { AbiVariant } from './abi-variant'; + +/** + * ABI entity + * @class + */ +export class Abi { + private typesMap: Map; + /** + * + * @param {string} version + * @param {AbiType[]} types + * @param {AbiStruct[]} structs + * @param {AbiAction[]} actions + * @param {AbiTable[]} tables + * @param {RicardianClause[]} ricardianClauses + * @param {AbiExtension[]} abiExtensions + * @param {AbiErrorMessage[]} errorMessages + * @param {string} comment + * @param {AbiVariant[]} variants + */ + private constructor( + public readonly version: string, + public readonly types: AbiType[], + public readonly structs: AbiStruct[], + public readonly tables: AbiTable[], + public readonly actions: AbiAction[], + public readonly ricardianClauses: RicardianClause[], + public readonly abiExtensions: AbiExtension[], + public readonly errorMessages: AbiErrorMessage[], + public readonly variants?: AbiVariant[] + ) { + this.typesMap = getTypesFromAbi(Serialize.createInitialTypes(), this.toDto()); + } + + /** + * Parse ABI entity to DTO + * @returns {AbiDto} + */ + public toDto(): AbiDto { + const { + version, + types, + structs, + actions, + tables, + ricardianClauses, + abiExtensions, + errorMessages: AbierrorMessages, + variants, + } = this; + + const dto: AbiDto = { + version, + types: types.map(item => item.toDto()), + structs: structs.map(item => item.toDto()), + tables: tables.map(item => item.toDto()), + actions: actions ? actions.map(item => item.toDto()) : [], + ricardian_clauses: ricardianClauses + ? ricardianClauses.map(item => item.toDto()) + : [], + abi_extensions: abiExtensions ? abiExtensions.map(item => item.toDto()) : [], + error_messages: AbierrorMessages ? AbierrorMessages.map(item => item.toDto()) : [], + variants: variants ? variants.map(item => item.toDto()) : [], + }; + + return dto; + } + + public getTypesMap(): Map { + return this.typesMap; + } + + /** + * Create ABI entity based on provided DTO + * + * @static + * @param {AbiDto} dto + * @returns {Abi} + */ + public static fromDto(dto: AbiDto): Abi { + const { version, types, structs, tables } = dto; + const actions = dto.actions ? dto.actions.map(dto => AbiAction.fromDto(dto)) : []; + const ricardian_clauses = dto.ricardian_clauses + ? dto.ricardian_clauses.map(dto => RicardianClause.fromDto(dto)) + : []; + const abi_extensions = dto.abi_extensions + ? dto.abi_extensions.map(dto => AbiExtension.fromDto(dto)) + : []; + const error_messages = dto.error_messages + ? dto.error_messages.map(dto => AbiErrorMessage.fromDto(dto)) + : []; + const variants = dto.variants ? dto.variants.map(dto => AbiVariant.fromDto(dto)) : []; + + return new Abi( + version, + types.map(dto => AbiType.fromDto(dto)), + structs.map(dto => AbiStruct.fromDto(dto)), + tables.map(dto => AbiTable.fromDto(dto)), + actions, + ricardian_clauses, + abi_extensions, + error_messages, + variants + ); + } +} diff --git a/src/common/blockchain/block-content/abi/index.ts b/src/common/blockchain/block-content/abi/index.ts new file mode 100644 index 0000000..545c409 --- /dev/null +++ b/src/common/blockchain/block-content/abi/index.ts @@ -0,0 +1,10 @@ +export { RicardianClause } from "./ricardian-clause"; +export { Abi } from "./abi"; +export { AbiAction } from "./abi-action"; +export { AbiErrorMessage } from "./abi-error-message"; +export { AbiExtension } from "./abi-extension"; +export { AbiStruct } from "./abi-struct"; +export { AbiTable } from "./abi-table"; +export { AbiType } from "./abi-type"; +export { AbiVariant } from "./abi-variant"; +export * from "./abi.dtos"; diff --git a/src/common/blockchain/block-content/abi/ricardian-clause.ts b/src/common/blockchain/block-content/abi/ricardian-clause.ts new file mode 100644 index 0000000..efc64a0 --- /dev/null +++ b/src/common/blockchain/block-content/abi/ricardian-clause.ts @@ -0,0 +1,31 @@ +import { RicardianClauseDto } from './abi.dtos'; + +/** + * @class + */ +export class RicardianClause { + /** + * + * @param {string} id + * @param {string} body + */ + private constructor(public readonly id: string, public readonly body: string) {} + + /** + * @returns {RicardianClauseDto} + */ + public toDto(): RicardianClauseDto { + const { id, body } = this; + return { id, body }; + } + + /** + * @static + * @param {RicardianClauseDto} dto + * @returns {RicardianClause} + */ + public static fromDto(dto: RicardianClauseDto): RicardianClause { + const { id, body } = dto; + return new RicardianClause(id, body); + } +} diff --git a/src/common/blockchain/block-content/action-trace/__tests__/action-trace.unit.test.ts b/src/common/blockchain/block-content/action-trace/__tests__/action-trace.unit.test.ts new file mode 100644 index 0000000..2e5c73e --- /dev/null +++ b/src/common/blockchain/block-content/action-trace/__tests__/action-trace.unit.test.ts @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Act, ActionTrace, Receipt } from '../action-trace'; +import { ActDto, ActionTraceDto } from '../action-trace.dtos'; + +const actDto: ActDto = { + account: 'foo.account', + name: 'foo.name', + authorization: { + actor: 'foo.actor', + permission: 'foo.permission', + }, + data: [] as any, +}; + +const receiptDto = { + receiver: 'foo', + act_digest: 'act', + global_sequence: '100', + recv_sequence: '100', + auth_sequence: [{ account: 'foo', sequence: 'foo_sequence' }], + code_sequence: 100, + abi_sequence: 100, +}; + +describe('Act Unit tests', () => { + it('"create" should create entity', () => { + const entity = Act.create(actDto); + expect(entity.account).toEqual(actDto.account); + expect(entity.name).toEqual(actDto.name); + expect(entity.authorization).toEqual(actDto.authorization); + expect(entity.data).toEqual(actDto.data); + }); +}); + +describe('Receipt Unit tests', () => { + it('"create" should create entity', () => { + const entity = Receipt.create('foo', receiptDto); + + expect(entity).not.toBeUndefined(); + }); +}); + +const actionTraceDto: ActionTraceDto = { + action_ordinal: 1, + creator_action_ordinal: 1, + receipt: ['foo', receiptDto], + receiver: 'receiver', + act: actDto, + context_free: true, + elapsed: 'elapsed', + console: 'foo_console', + account_ram_deltas: [], + except: '', + error_code: '200', +}; + +describe('ActionTrace Unit tests', () => { + it('"create" should create entity', () => { + const entity = ActionTrace.create('foo', actionTraceDto); + + expect(entity).not.toBeUndefined(); + }); +}); diff --git a/src/common/blockchain/block-content/action-trace/action-trace.dtos.ts b/src/common/blockchain/block-content/action-trace/action-trace.dtos.ts new file mode 100644 index 0000000..4707245 --- /dev/null +++ b/src/common/blockchain/block-content/action-trace/action-trace.dtos.ts @@ -0,0 +1,44 @@ +export type AuthSequenceDto = { + account: string; + sequence: string; +}; + +export type ReceiptDto = { + receiver: string; + act_digest: string; + global_sequence: string; + recv_sequence: string; + auth_sequence: AuthSequenceDto[]; + code_sequence: number; + abi_sequence: number; +}; + +export type ReceiptByNameDto = [string, ReceiptDto]; + +export type ActAuthDto = { + actor: string; + permission: string; +}; + +export type ActDto = { + account: string; + name: string; + authorization: ActAuthDto; + data: Uint8Array; +}; + +export type ActionTraceDto = { + action_ordinal: number; + creator_action_ordinal: number; + receipt: ReceiptByNameDto; + receiver: string; + act: ActDto; + context_free: boolean; + elapsed: string; + console: string; + account_ram_deltas: unknown[]; + except: unknown; + error_code: string | number; +}; + +export type ActionTraceByNameDto = [string, ActionTraceDto]; diff --git a/src/common/blockchain/block-content/action-trace/action-trace.ts b/src/common/blockchain/block-content/action-trace/action-trace.ts new file mode 100644 index 0000000..3a259db --- /dev/null +++ b/src/common/blockchain/block-content/action-trace/action-trace.ts @@ -0,0 +1,127 @@ +import { parseToBigInt } from '@alien-worlds/api-core'; +import { ActAuthDto, ActDto, ActionTraceDto, ReceiptDto } from './action-trace.dtos'; + +export class ActAuth { + public static create(dto: ActAuthDto): ActAuth { + const { actor, permission } = dto; + + return new ActAuth(actor, permission); + } + private constructor( + public readonly actor: string, + public readonly permission: string + ) {} +} + +export class Act { + public static create(dto: ActDto): Act { + const { account, name, data } = dto; + + //parse DATA + let authorization: ActAuth; + + if (dto.authorization) { + authorization = ActAuth.create(dto.authorization); + } + + return new Act(account, name, authorization, data); + } + private constructor( + public readonly account: string, + public readonly name: string, + public readonly authorization: ActAuth, + public readonly data: Uint8Array + ) {} +} + +export type AuthSequence = { + account: string; + sequence: string; +}; + +export class Receipt { + public static create(type: string, dto: ReceiptDto): Receipt { + const { + receiver, + act_digest, + global_sequence, + recv_sequence, + auth_sequence, + code_sequence, + abi_sequence, + } = dto; + return new Receipt( + type, + receiver, + act_digest, + parseToBigInt(global_sequence), + parseToBigInt(recv_sequence), + auth_sequence, + code_sequence, + abi_sequence + ); + } + private constructor( + public readonly type: string, + public readonly receiver: string, + public readonly actDigest: string, + public readonly globalSequence: bigint, + public readonly recvSequence: bigint, + public readonly authSequence: AuthSequence[], + public readonly codeSequence: number, + public readonly abiSequence: number + ) {} +} + +export class ActionTrace { + public static create(type: string, dto: ActionTraceDto): ActionTrace { + const { + action_ordinal, + creator_action_ordinal, + receiver, + act, + context_free, + elapsed, + console, + account_ram_deltas, + except, + error_code, + } = dto; + + let receipt: Receipt; + if (dto.receipt && dto.receipt.length) { + const [receiptType, receiptContent] = dto.receipt; + receipt = Receipt.create(receiptType, receiptContent); + } + + return new ActionTrace( + type, + action_ordinal, + creator_action_ordinal, + receipt, + receiver, + Act.create(act), + context_free, + elapsed, + console, + account_ram_deltas, + except, + Number(error_code) + ); + } + + private constructor( + public readonly type: string, + public readonly actionOrdinal: number, + public readonly creatorActionOrdinal: number, + public readonly receipt: Receipt | null, + public readonly receiver: string, + public readonly act: Act, + public readonly isContextFree: boolean, + public readonly elapsed: string, + public readonly console: string, + public readonly accountRamDeltas: unknown[], + public readonly except: unknown, + public readonly error_code: number + ) {} +} diff --git a/src/common/blockchain/block-content/action-trace/index.ts b/src/common/blockchain/block-content/action-trace/index.ts new file mode 100644 index 0000000..986e248 --- /dev/null +++ b/src/common/blockchain/block-content/action-trace/index.ts @@ -0,0 +1,2 @@ +export * from './action-trace'; +export * from './action-trace.dtos'; \ No newline at end of file diff --git a/src/common/blockchain/block-content/block/__tests__/block.unit.test.ts b/src/common/blockchain/block-content/block/__tests__/block.unit.test.ts new file mode 100644 index 0000000..f581fe8 --- /dev/null +++ b/src/common/blockchain/block-content/block/__tests__/block.unit.test.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Block } from '../block'; +const dto = { + timestamp: '2022-06-30T12:28:32.900Z', + producer: 'some_producer', + confirmed: 0, + previous: 'previous_value', + transaction_mroot: 'mroot', + action_mroot: 'action', + schedule_version: 1, + new_producers: '', + header_extensions: [], + producer_signature: 'prod', + transactions: [ + { + status: 0, + cpu_usage_us: 1, + net_usage_words: 1, + trx: ['', ''], + }, + ] as any, +}; + +describe('Block Unit tests', () => { + beforeAll(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date(2022, 4, 5)); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + it('"create" should create Block entity based on given DTO', async () => { + const entity = Block.create(dto); + expect(entity).toBeInstanceOf(Block); + }); + + it('"create" should use system current timestamp if DTO does not have one', async () => { + dto.timestamp = ''; + const entity = Block.create(dto); + expect(entity.timestamp.toISOString()).toEqual('2022-05-04T22:00:00.000Z'); + expect(entity).toBeInstanceOf(Block); + }); +}); diff --git a/src/common/blockchain/block-content/block/block.dtos.ts b/src/common/blockchain/block-content/block/block.dtos.ts new file mode 100644 index 0000000..02a6d43 --- /dev/null +++ b/src/common/blockchain/block-content/block/block.dtos.ts @@ -0,0 +1,15 @@ +import { TransactionDto } from '../transaction/transaction.dtos'; + +export type BlockDto = { + timestamp: string; + producer: string; + confirmed: number; + previous: string; + transaction_mroot: string; + action_mroot: string; + schedule_version: number; + new_producers: unknown; + header_extensions: unknown[]; + producer_signature: string; + transactions: TransactionDto[]; +}; diff --git a/src/common/blockchain/block-content/block/block.ts b/src/common/blockchain/block-content/block/block.ts new file mode 100644 index 0000000..edc50b6 --- /dev/null +++ b/src/common/blockchain/block-content/block/block.ts @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +import { parseDateToMs } from '@alien-worlds/api-core'; +import { Transaction } from '../transaction/transaction'; +import { BlockDto } from './block.dtos'; + +export class Block { + public static create(dto: BlockDto): Block { + const { + producer, + confirmed, + previous, + transaction_mroot, + action_mroot, + schedule_version, + new_producers, + header_extensions, + producer_signature, + transactions, + } = dto; + + const timestamp = dto.timestamp + ? new Date(parseDateToMs(dto.timestamp.replace(/(\.000|\.500)/g, 'Z'))) + : new Date(); + + return new Block( + timestamp, + producer, + confirmed, + previous, + transaction_mroot, + action_mroot, + schedule_version, + new_producers, + header_extensions, + producer_signature, + transactions.map(dto => Transaction.create(dto)) + ); + } + + private constructor( + public readonly timestamp: Date, + public readonly producer: string, + public readonly confirmed: number, + public readonly previous: string, + public readonly transactionMroot: string, + public readonly actionMroot: string, + public readonly scheduleVersion: number, + public readonly newProducers: unknown, + public readonly headerExtensions: unknown[], + public readonly producerSignature: string, + public readonly transactions: Transaction[] + ) {} +} diff --git a/src/common/blockchain/block-content/block/index.ts b/src/common/blockchain/block-content/block/index.ts new file mode 100644 index 0000000..6728bed --- /dev/null +++ b/src/common/blockchain/block-content/block/index.ts @@ -0,0 +1,2 @@ +export * from './block'; +export * from './block.dtos'; \ No newline at end of file diff --git a/src/common/blockchain/block-content/delta/__tests__/delta.unit.test.ts b/src/common/blockchain/block-content/delta/__tests__/delta.unit.test.ts new file mode 100644 index 0000000..41a198f --- /dev/null +++ b/src/common/blockchain/block-content/delta/__tests__/delta.unit.test.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Delta } from '../delta'; + +describe('Delta Unit tests', () => { + it('"create" should create Delta entity based on given DTO', async () => { + const entity = Delta.create('foo', { + name: 'delta', + rows: [{ present: 1, data: Uint8Array.from([]) }], + }); + expect(entity).toBeInstanceOf(Delta); + }); +}); diff --git a/src/common/blockchain/block-content/delta/delta.dtos.ts b/src/common/blockchain/block-content/delta/delta.dtos.ts new file mode 100644 index 0000000..6773db4 --- /dev/null +++ b/src/common/blockchain/block-content/delta/delta.dtos.ts @@ -0,0 +1,11 @@ +export type DeltaRowDto = { + present: number; + data: Uint8Array; +}; + +export type DeltaDto = { + name: string; + rows: DeltaRowDto[]; +}; + +export type DeltaByNameDto = [string, DeltaDto]; diff --git a/src/common/blockchain/block-content/delta/delta.ts b/src/common/blockchain/block-content/delta/delta.ts new file mode 100644 index 0000000..f156476 --- /dev/null +++ b/src/common/blockchain/block-content/delta/delta.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +import { DeltaDto, DeltaRowDto } from './delta.dtos'; + +export class DeltaRow { + public static create(dto: DeltaRowDto): DeltaRow { + const { present, data } = dto; + return new DeltaRow(present, data); + } + + private constructor( + public readonly present: number, + public readonly data: Uint8Array + ) {} +} + +export class Delta { + public static create(type: string, dto: DeltaDto): Delta { + const { name, rows } = dto; + + return new Delta( + type, + name, + rows.map(dto => DeltaRow.create(dto)) + ); + } + + private constructor( + public readonly type: string, + public readonly name: string, + public readonly rows: DeltaRow[] + ) {} +} diff --git a/src/common/blockchain/block-content/delta/index.ts b/src/common/blockchain/block-content/delta/index.ts new file mode 100644 index 0000000..248ee3a --- /dev/null +++ b/src/common/blockchain/block-content/delta/index.ts @@ -0,0 +1,2 @@ +export * from './delta'; +export * from './delta.dtos'; \ No newline at end of file diff --git a/src/common/blockchain/block-content/index.ts b/src/common/blockchain/block-content/index.ts new file mode 100644 index 0000000..d4e84bf --- /dev/null +++ b/src/common/blockchain/block-content/index.ts @@ -0,0 +1,6 @@ +export * from './abi'; +export * from './action-trace/action-trace'; +export * from './block/block'; +export * from './delta/delta'; +export * from './trace/trace'; +export * from './transaction/transaction'; \ No newline at end of file diff --git a/src/common/blockchain/block-content/trace/__tests__/trace.unit.test.ts b/src/common/blockchain/block-content/trace/__tests__/trace.unit.test.ts new file mode 100644 index 0000000..0431de4 --- /dev/null +++ b/src/common/blockchain/block-content/trace/__tests__/trace.unit.test.ts @@ -0,0 +1,68 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Trace } from '../trace'; + +const dto = { + id: 'foo', + status: 0, + cpu_usage_us: 100, + net_usage_words: 100, + elapsed: '', + net_usage: '100', + scheduled: false, + action_traces: [ + [ + 'action', + { + action_ordinal: 0, + creator_action_ordinal: 0, + receipt: [ + 'foo_receipt', + { + receiver: 'receiver', + act_digest: '', + global_sequence: '', + recv_sequence: '', + auth_sequence: [], + code_sequence: 0, + abi_sequence: 10, + }, + ], + receiver: 'receiver', + act: {}, + context_free: false, + elapsed: 'elapsed', + console: 'console', + account_ram_deltas: [], + except: '', + error_code: '200', + }, + ], + ] as any, + account_ram_delta: '', + except: '', + error_code: 0, + failed_dtrx_trace: '', + partial: [ + 'foo', + { + expiration: '10000', + ref_block_num: 0, + ref_block_prefix: 0, + max_net_usage_words: 0, + max_cpu_usage_ms: 0, + delay_sec: 1, + transaction_extensions: [], + signatures: [], + context_free_data: [], + }, + ] as any, +}; + +describe('Trace Unit tests', () => { + it('"create" should create Trace entity based on given DTO', async () => { + const entity = Trace.create('foo', dto); + expect(entity).toBeInstanceOf(Trace); + }); +}); diff --git a/src/common/blockchain/block-content/trace/index.ts b/src/common/blockchain/block-content/trace/index.ts new file mode 100644 index 0000000..17d6a1a --- /dev/null +++ b/src/common/blockchain/block-content/trace/index.ts @@ -0,0 +1,2 @@ +export * from './trace'; +export * from './trace.dtos'; \ No newline at end of file diff --git a/src/common/blockchain/block-content/trace/trace.dtos.ts b/src/common/blockchain/block-content/trace/trace.dtos.ts new file mode 100644 index 0000000..a387282 --- /dev/null +++ b/src/common/blockchain/block-content/trace/trace.dtos.ts @@ -0,0 +1,33 @@ +import { ActionTraceByNameDto } from "../action-trace"; + +export type PartialDto = { + expiration: string; + ref_block_num: number; + ref_block_prefix: number; + max_net_usage_words: number; + max_cpu_usage_ms: number; + delay_sec: number; + transaction_extensions: unknown[]; + signatures: unknown[]; + context_free_data: unknown[]; +}; + +export type PartialByTypeDto = [string, PartialDto]; + +export type TraceDto = { + id: string; + status: number; + cpu_usage_us: number; + net_usage_words: number; + elapsed: string; + net_usage: string; + scheduled: boolean; + action_traces: ActionTraceByNameDto[]; + account_ram_delta: unknown; + except: unknown; + error_code: number | string; + failed_dtrx_trace: unknown; + partial: PartialByTypeDto; +}; + +export type TraceByNameDto = [string, TraceDto]; diff --git a/src/common/blockchain/block-content/trace/trace.ts b/src/common/blockchain/block-content/trace/trace.ts new file mode 100644 index 0000000..157e2fc --- /dev/null +++ b/src/common/blockchain/block-content/trace/trace.ts @@ -0,0 +1,107 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +import { ActionTrace } from '../action-trace/action-trace'; +import { PartialDto, TraceDto } from './trace.dtos'; + +export class Partial { + public static create(type: string, dto: PartialDto): Partial { + const { + expiration, + ref_block_num, + ref_block_prefix, + max_net_usage_words, + max_cpu_usage_ms, + delay_sec, + transaction_extensions, + signatures, + context_free_data, + } = dto; + return new Partial( + type, + expiration, + ref_block_num, + ref_block_prefix, + max_net_usage_words, + max_cpu_usage_ms, + delay_sec, + transaction_extensions, + signatures, + context_free_data + ); + } + private constructor( + public readonly name: string, + public readonly expiration: string, + public readonly refBlockNumber: number, + public readonly refBlockPrefix: number, + public readonly maxNetUsageWords: number, + public readonly maxCpuUsageMs: number, + public readonly delayInSeconds: number, + public readonly transactionExtensions: unknown[], + public readonly signatures: unknown[], + public readonly contextFreeData: unknown[] + ) {} +} + +export class Trace { + public static create(type: string, traceDto: TraceDto): Trace { + const { + id, + status, + cpu_usage_us, + net_usage_words, + elapsed, + net_usage, + scheduled, + action_traces, + account_ram_delta, + except, + error_code, + failed_dtrx_trace, + } = traceDto; + + const actionTraces = action_traces.map(item => { + const [actionTraceType, actionTraceDto] = item; + return ActionTrace.create(actionTraceType, actionTraceDto); + }); + let partial: Partial; + if (traceDto.partial) { + const [partialType, partialContent] = traceDto.partial; + partial = Partial.create(partialType, partialContent); + } + + return new Trace( + type, + id, + status, + cpu_usage_us, + net_usage_words, + elapsed, + net_usage, + scheduled, + actionTraces, + account_ram_delta, + except, + Number(error_code), + failed_dtrx_trace, + partial + ); + } + + private constructor( + public readonly type: string, + public readonly id: string, + public readonly status: number, + public readonly cpuUsageUs: number, + public readonly netUsageWords: number, + public readonly elapsed: string, + public readonly netUsage: string, + public readonly scheduled: boolean, + public readonly actionTraces: ActionTrace[], + public readonly accountRamDelta: unknown, + public readonly except: unknown, + public readonly errorCode: number, + public readonly failedDtrxTrace: unknown, + public readonly partial: Partial | null + ) {} +} diff --git a/src/common/blockchain/block-content/transaction/__tests__/transaction.unit.test.ts b/src/common/blockchain/block-content/transaction/__tests__/transaction.unit.test.ts new file mode 100644 index 0000000..c65bad9 --- /dev/null +++ b/src/common/blockchain/block-content/transaction/__tests__/transaction.unit.test.ts @@ -0,0 +1,44 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Transaction } from '../transaction'; + +const dto = { + status: 0, + cpu_usage_us: 1, + net_usage_words: 2, + trx: ['', ''] as any, +}; + +describe('Transaction Unit tests', () => { + it('"create" should create Transaction entity based on given DTO', async () => { + const entity = Transaction.create(dto); + expect(entity).toBeInstanceOf(Transaction); + }); + + it('"create" should log warning on unknown trx type', async () => { + dto.trx = ['foo', '']; + const entity = Transaction.create(dto); + expect(entity).toBeInstanceOf(Transaction); + }); + + it('"create" should create Trx entity when trx type is "transaction_id"', async () => { + dto.trx = ['transaction_id', 'content']; + const entity = Transaction.create(dto); + expect(entity).toBeInstanceOf(Transaction); + }); + + it('"create" should create PackedTrx entity when trx type is "transaction_id"', async () => { + dto.trx = [ + 'packed_transaction', + { + signatures: [], + compression: 0, + packed_context_free_data: '', + packed_trx: Uint8Array.from([]), + }, + ]; + const entity = Transaction.create(dto); + expect(entity).toBeInstanceOf(Transaction); + }); +}); diff --git a/src/common/blockchain/block-content/transaction/index.ts b/src/common/blockchain/block-content/transaction/index.ts new file mode 100644 index 0000000..709ffa3 --- /dev/null +++ b/src/common/blockchain/block-content/transaction/index.ts @@ -0,0 +1,2 @@ +export * from './transaction'; +export * from './transaction.dtos'; diff --git a/src/common/blockchain/block-content/transaction/transaction.dtos.ts b/src/common/blockchain/block-content/transaction/transaction.dtos.ts new file mode 100644 index 0000000..adcab21 --- /dev/null +++ b/src/common/blockchain/block-content/transaction/transaction.dtos.ts @@ -0,0 +1,14 @@ +export type PackedTrxDto = { + signatures: string[]; + compression: number; + packed_context_free_data: unknown; + packed_trx: Uint8Array; +}; +export type TrxByNameDto = [string, PackedTrxDto | string]; + +export type TransactionDto = { + status: number; + cpu_usage_us: number; + net_usage_words: number; + trx: TrxByNameDto; +}; diff --git a/src/common/blockchain/block-content/transaction/transaction.ts b/src/common/blockchain/block-content/transaction/transaction.ts new file mode 100644 index 0000000..843c90a --- /dev/null +++ b/src/common/blockchain/block-content/transaction/transaction.ts @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ + +import { PackedTrxDto, TransactionDto } from "./transaction.dtos"; + +export class PackedTrx { + public static create(type: string, dto: PackedTrxDto): PackedTrx { + const { signatures, compression, packed_context_free_data, packed_trx } = dto; + return new PackedTrx( + type, + signatures, + compression, + packed_context_free_data, + packed_trx + ); + } + + private constructor( + public readonly type: string, + public readonly signatures: string[], + public readonly compression: number, + public readonly packedContextFreeData: unknown, + public readonly content: unknown //TODO: we should deserialize "packed_trx" + ) {} +} + +export class Trx { + public static create(type: string, dto: string): Trx { + return new Trx(type, dto); + } + + private constructor(public readonly type: string, public readonly content: string) {} +} + +export class Transaction { + public static create(dto: TransactionDto): Transaction { + const { status, cpu_usage_us, net_usage_words } = dto; + + const [type, content] = dto.trx; + let trx; + + switch (type) { + case 'transaction_id': { + trx = Trx.create(type, content); + break; + } + case 'packed_transaction': { + trx = PackedTrx.create(type, content); + break; + } + default: { + console.warn(`Unknown trx type "${type}"`); + } + } + return new Transaction(status, cpu_usage_us, net_usage_words, trx); + } + + private constructor( + public readonly status: number, + public readonly cpuUsageUs: number, + public readonly netUsageWords: number, + public readonly trx: Trx | PackedTrx | unknown + ) {} +} diff --git a/src/common/blockchain/block-reader/__tests__/block-reader.source.unit.test.ts b/src/common/blockchain/block-reader/__tests__/block-reader.source.unit.test.ts new file mode 100644 index 0000000..6a4ee8e --- /dev/null +++ b/src/common/blockchain/block-reader/__tests__/block-reader.source.unit.test.ts @@ -0,0 +1,219 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { BlockReaderConnectionState } from "../block-reader.enums"; +import { BlockReaderSource } from "../block-reader.source"; + + +const webSocketMock = { + on: jest.fn(), + once: jest.fn((type, callback) => { + callback(true); + }), + close: jest.fn(), + send: jest.fn(), + removeAllListeners: jest.fn(), +}; + +jest.mock('ws', () => jest.fn().mockImplementation(() => webSocketMock)); + +describe('BlockReaderSource Unit tests', () => { + it('"updateConnectionState" should return a handler assigned to the connection state', () => { + const source = new BlockReaderSource({} as any); + const handler = jest.fn(); + source.addConnectionStateHandler(BlockReaderConnectionState.Connected, handler); + + (source as any).updateConnectionState(BlockReaderConnectionState.Connected); + + expect(handler).toBeCalled(); + }); + + it('"getNextEndpoint" should return first endpoint form the list', () => { + const source = new BlockReaderSource({ shipEndpoints: ['foo', 'bar'] } as any); + const endpoint = (source as any).getNextEndpoint(); + + expect(endpoint).toEqual('foo'); + }); + + it('"getNextEndpoint" should return last endpoint form the list', () => { + const shipEndpoints = ['foo', 'bar']; + const source = new BlockReaderSource({ shipEndpoints } as any); + let endpoint; + + for (let i = 0; i < shipEndpoints.length; i++) { + endpoint = (source as any).getNextEndpoint(); + } + + expect(endpoint).toEqual('bar'); + }); + + it('"getNextEndpoint" should return first endpoint form the list when it went through the entire list', () => { + const source = new BlockReaderSource({ shipEndpoints: ['foo', 'bar'] } as any); + (source as any).getNextEndpoint(); + (source as any).getNextEndpoint(); + const endpoint = (source as any).getNextEndpoint(); + + expect(endpoint).toEqual('foo'); + }); + + it('"onError" should set error handler', () => { + const source = new BlockReaderSource({} as any); + source.onError(jest.fn()); + + expect((source as any).errorHandler).toBeTruthy(); + }); + + it('"onMessage" should set message handler', () => { + const source = new BlockReaderSource({} as any); + source.onMessage(jest.fn()); + + expect((source as any).messageHandler).toBeTruthy(); + }); + + it('"addConnectionStateHandler" should log warning if there is already a handler assigned to the state', () => { + const source = new BlockReaderSource({} as any); + const warnMock = jest.spyOn(console, 'warn'); + + source.addConnectionStateHandler(BlockReaderConnectionState.Connected, jest.fn()); + source.addConnectionStateHandler(BlockReaderConnectionState.Connected, jest.fn()); + + expect(warnMock).toBeCalled(); + + warnMock.mockClear(); + }); + + it('"addConnectionStateHandler" should assign a handler to the state', () => { + const source = new BlockReaderSource({} as any); + + source.addConnectionStateHandler(BlockReaderConnectionState.Connected, jest.fn()); + + expect( + (source as any).connectionChangeHandlers.has(BlockReaderConnectionState.Connected) + ).toBeTruthy(); + }); + + it('"isConnected" should return true if source is connected', () => { + const source = new BlockReaderSource({} as any); + (source as any).connectionState = BlockReaderConnectionState.Connected; + + expect(source.isConnected).toBeTruthy(); + }); + + it('"isConnected" should return false if source is not connected', () => { + const source = new BlockReaderSource({} as any); + (source as any).connectionState = BlockReaderConnectionState.Idle; + + expect(source.isConnected).toBeFalsy(); + }); + + it('"connect" should change connection status to "connecting" and then after successful connection to "connected"', async () => { + const source = new BlockReaderSource({} as any); + const updateConnectionStateMock = jest.fn(); + + (source as any).errorHandler = jest.fn(); + (source as any).getNextEndpoint = jest.fn(); + (source as any).updateConnectionState = updateConnectionStateMock; + (source as any).waitUntilConnectionIsOpen = jest.fn(); + (source as any).receiveAbi = jest.fn().mockResolvedValue(''); + + await source.connect(); + + expect(updateConnectionStateMock).toBeCalledTimes(2); + }); + + it('"connect" should call error handler on any error', async () => { + const source = new BlockReaderSource({} as any); + + (source as any).errorHandler = jest.fn(); + (source as any).getNextEndpoint = jest.fn().mockImplementation(() => { + throw new Error(); + }); + + await source.connect(); + + expect((source as any).errorHandler).toBeCalled(); + }); + + it('"connect" should wait until connection is open and receive first message as ABI', async () => { + const source = new BlockReaderSource({} as any); + const updateConnectionStateMock = jest.fn(); + + (source as any).errorHandler = jest.fn(); + (source as any).getNextEndpoint = jest.fn(); + (source as any).updateConnectionState = updateConnectionStateMock; + (source as any).waitUntilConnectionIsOpen = jest.fn(); + (source as any).receiveAbi = jest.fn().mockResolvedValue(''); + + await source.connect(); + + expect((source as any).waitUntilConnectionIsOpen).toBeCalled(); + expect((source as any).receiveAbi).toBeCalled(); + }); + + it('"disconnect" should change connection status to "disconnecting" and then after successful disconnection to "idle"', async () => { + const source = new BlockReaderSource({} as any); + const updateConnectionStateMock = jest.fn(); + + (source as any).connectionState = BlockReaderConnectionState.Connected; + (source as any).client = webSocketMock; + (source as any).errorHandler = jest.fn(); + (source as any).updateConnectionState = updateConnectionStateMock; + (source as any).waitUntilConnectionIsClosed = jest.fn(); + + await source.disconnect(); + + expect(webSocketMock.close).toBeCalled(); + expect((source as any).client).toBeNull(); + expect(updateConnectionStateMock).toBeCalledTimes(2); + }); + + it('"disconnect" should call error handler on any error', async () => { + const source = new BlockReaderSource({} as any); + + (source as any).connectionState = BlockReaderConnectionState.Connected; + (source as any).errorHandler = jest.fn(); + (source as any).client = webSocketMock; + (source as any).client.close.mockImplementation(() => { + throw new Error(); + }); + + await source.disconnect(); + + expect((source as any).errorHandler).toBeCalled(); + }); + + it('"send" should call client.send', async () => { + const source = new BlockReaderSource({} as any); + (source as any).client = webSocketMock; + + await source.send(new Uint8Array()); + + expect((source as any).client.send).toBeCalled(); + }); + + it('"waitUntilConnectionIsOpen" should resolve a promise on "open" handler', async () => { + const source = new BlockReaderSource({} as any); + (source as any).client = webSocketMock; + + const result = await (source as any).waitUntilConnectionIsOpen(); + + expect(result).toBeTruthy(); + }); + it('"waitUntilConnectionIsClosed" should resolve a promise on "close" handler', async () => { + const source = new BlockReaderSource({} as any); + (source as any).client = webSocketMock; + + const result = await (source as any).waitUntilConnectionIsClosed(); + + expect(result).toBeTruthy(); + }); + it('"receiveAbi" should resolve a promise once on "message" handler', async () => { + const source = new BlockReaderSource({} as any); + (source as any).client = webSocketMock; + + const result = await (source as any).receiveAbi(); + + expect(result).toBeTruthy(); + }); +}); diff --git a/src/common/blockchain/block-reader/__tests__/block-reader.utils.unit.test.ts b/src/common/blockchain/block-reader/__tests__/block-reader.utils.unit.test.ts new file mode 100644 index 0000000..ccabec8 --- /dev/null +++ b/src/common/blockchain/block-reader/__tests__/block-reader.utils.unit.test.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Serialize } from 'eosjs'; +import { deserializeMessage, serializeMessage } from '../block-reader.utils'; + +jest.mock('eosjs'); + +const SerialBufferMock = Serialize.SerialBuffer as jest.MockedClass< + typeof Serialize.SerialBuffer +>; + +const type = { + name: 'foo', + aliasOfName: 'foo', + arrayOf: {}, + optionalOf: {}, + baseName: 'foo', + base: {}, + fields: [], + serialize: jest.fn(), + deserialize: jest.fn(), +}; + +const typesByName = new Map([['foo', type]]); + +describe('State History Service Unit tests', () => { + it('"serializeMessage" should return buffer as Uint8Array', () => { + SerialBufferMock.prototype.asUint8Array.mockReturnValue(Uint8Array.from([])); + (Serialize.getType as any) = jest.fn().mockReturnValue(type); + + const result = serializeMessage('foo', '', typesByName as any); + expect(result).toBeInstanceOf(Uint8Array); + }); + + it('"deserializeMessage" should return deserialized data', () => { + const data = { versions: '1.0' }; + const readPos = 18; + const encoded = new TextEncoder().encode(JSON.stringify(data)); + + type.deserialize.mockReturnValue(data); + SerialBufferMock.prototype.asUint8Array.mockReturnValue(encoded); + SerialBufferMock.prototype.readPos = readPos; + (Serialize.getType as any) = jest.fn().mockReturnValue(type); + + const result = deserializeMessage('foo', encoded, typesByName as any); + expect(result).toEqual(data); + }); +}); diff --git a/src/common/blockchain/block-reader/block-reader.config.ts b/src/common/blockchain/block-reader/block-reader.config.ts new file mode 100644 index 0000000..7d3293c --- /dev/null +++ b/src/common/blockchain/block-reader/block-reader.config.ts @@ -0,0 +1,5 @@ +export type BlockReaderConfig = { + shipEndpoints: string[]; + shouldFetchDeltas?: boolean; + shouldFetchTraces?: boolean; +}; diff --git a/src/common/blockchain/block-reader/block-reader.dtos.ts b/src/common/blockchain/block-reader/block-reader.dtos.ts new file mode 100644 index 0000000..6a44e31 --- /dev/null +++ b/src/common/blockchain/block-reader/block-reader.dtos.ts @@ -0,0 +1,14 @@ +export type BlockNumberWithIdDto = { + block_num: number; + block_id: string; +}; + +export type GetBlocksResultDto = { + head: BlockNumberWithIdDto; + this_block: BlockNumberWithIdDto; + last_irreversible: BlockNumberWithIdDto; + prev_block: BlockNumberWithIdDto; + block: Uint8Array; + traces: Uint8Array; + deltas: Uint8Array; +}; diff --git a/src/common/blockchain/block-reader/block-reader.enums.ts b/src/common/blockchain/block-reader/block-reader.enums.ts new file mode 100644 index 0000000..41676cf --- /dev/null +++ b/src/common/blockchain/block-reader/block-reader.enums.ts @@ -0,0 +1,6 @@ +export enum BlockReaderConnectionState { + Connecting = 'connecting', + Connected = 'connected', + Idle = 'idle', + Disconnecting = 'disconnecting', +} diff --git a/src/common/blockchain/block-reader/block-reader.errors.ts b/src/common/blockchain/block-reader/block-reader.errors.ts new file mode 100644 index 0000000..5451dea --- /dev/null +++ b/src/common/blockchain/block-reader/block-reader.errors.ts @@ -0,0 +1,37 @@ +export class AbiNotFoundError extends Error { + constructor() { + super(`ABI data not found`); + } +} + +export class MissingHandlersError extends Error { + constructor() { + super('Set handlers before calling connect()'); + } +} + +export class ServiceNotConnectedError extends Error { + constructor() { + super(`Client is not connected, requestBlocks cannot be called`); + } +} + +export class UnhandledBlockRequestError extends Error { + constructor(start: bigint, end: bigint) { + super( + `Error sending the block_range request ${start.toString()}-${end.toString()}. The current request was not completed or canceled.` + ); + } +} + +export class UnhandledMessageTypeError extends Error { + constructor(public readonly type: string) { + super(`Unhandled message type: ${type}`); + } +} + +export class UnhandledMessageError extends Error { + constructor(public readonly message, public readonly error) { + super('Received a message while no block range is being processed'); + } +} diff --git a/src/common/blockchain/block-reader/block-reader.source.ts b/src/common/blockchain/block-reader/block-reader.source.ts new file mode 100644 index 0000000..684306e --- /dev/null +++ b/src/common/blockchain/block-reader/block-reader.source.ts @@ -0,0 +1,119 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ +import WebSocket from 'ws'; +import { BlockReaderConfig } from './block-reader.config'; +import { BlockReaderConnectionState } from './block-reader.enums'; +import { ConnectionChangeHandler } from './block-reader.types'; + +export class BlockReaderSource { + private messageHandler: (...args: unknown[]) => void; + private errorHandler: (...args: unknown[]) => void; + private client: WebSocket; + private connectionState = BlockReaderConnectionState.Idle; + private connectionChangeHandlers: Map< + BlockReaderConnectionState, + ConnectionChangeHandler + > = new Map(); + private socketIndex = -1; + + constructor(private readonly config: BlockReaderConfig) {} + + private async updateConnectionState(state: BlockReaderConnectionState, data?: string) { + const previousState = state; + this.connectionState = state; + + const handler = this.connectionChangeHandlers.get(state); + + if (handler) { + return handler({ previousState, state, data }); + } + } + + private getNextEndpoint() { + let nextIndex = ++this.socketIndex; + + if (nextIndex >= this.config.shipEndpoints.length) { + nextIndex = 0; + } + this.socketIndex = nextIndex; + + return this.config.shipEndpoints[this.socketIndex]; + } + + private waitUntilConnectionIsOpen() { + return new Promise(resolve => this.client.once('open', resolve)); + } + + private waitUntilConnectionIsClosed() { + return new Promise(resolve => this.client.once('close', resolve)); + } + + private receiveAbi() { + return new Promise(resolve => this.client.once('message', resolve)); + } + + public onError(handler: (error: Error) => void) { + this.errorHandler = handler; + } + + public onMessage(handler: (dto: Uint8Array) => void) { + this.messageHandler = handler; + } + + public addConnectionStateHandler( + state: BlockReaderConnectionState, + handler: ConnectionChangeHandler + ) { + if (this.connectionChangeHandlers.has(state)) { + console.warn(`Overriding the handler assigned to the "${state}" state`); + } else { + this.connectionChangeHandlers.set(state, handler); + } + } + + public get isConnected() { + return this.connectionState === BlockReaderConnectionState.Connected; + } + + public async connect() { + if (this.connectionState === BlockReaderConnectionState.Idle) { + try { + await this.updateConnectionState(BlockReaderConnectionState.Connecting); + this.client = new WebSocket(this.getNextEndpoint(), { + perMessageDeflate: false, + }); + this.client.on('error', error => this.errorHandler(error)); + await this.waitUntilConnectionIsOpen(); + // receive ABI - first message from WS is always ABI + const abi = await this.receiveAbi(); + // set message handler + this.client.on('message', message => this.messageHandler(message)); + + await this.updateConnectionState(BlockReaderConnectionState.Connected, abi); + } catch (error) { + this.errorHandler(error); + } + } + } + + public async disconnect() { + if (this.connectionState === BlockReaderConnectionState.Connected) { + try { + await this.updateConnectionState(BlockReaderConnectionState.Disconnecting); + this.client.removeAllListeners(); + this.client.close(); + await this.waitUntilConnectionIsClosed(); + this.client = null; + await this.updateConnectionState(BlockReaderConnectionState.Idle); + } catch (error) { + this.errorHandler(error); + } + } + } + + public send(message: Uint8Array) { + this.client.send(message); + } +} diff --git a/src/common/blockchain/block-reader/block-reader.ts b/src/common/blockchain/block-reader/block-reader.ts new file mode 100644 index 0000000..64bdf5b --- /dev/null +++ b/src/common/blockchain/block-reader/block-reader.ts @@ -0,0 +1,209 @@ +import { log } from '@alien-worlds/api-core'; +import { Abi, AbiDto } from '../block-content'; +import { BlockReaderConnectionState } from './block-reader.enums'; +import { + AbiNotFoundError, + MissingHandlersError, + UnhandledBlockRequestError, + UnhandledMessageError, + UnhandledMessageTypeError, +} from './block-reader.errors'; +import { BlockReaderSource } from './block-reader.source'; +import { BlockReaderOptions, ConnectionChangeHandlerOptions } from './block-reader.types'; +import { BlockReaderMessage } from './models/block-reader.message'; +import { GetBlocksAckRequest } from './models/get-blocks-ack.request'; +import { GetBlocksRequest } from './models/get-blocks.request'; +import { ReceivedBlock } from './models/received-block'; + +export abstract class BlockReader { + public abstract readBlocks( + startBlock: bigint, + endBlock: bigint, + options: BlockReaderOptions + ): void; + + public abstract onReceivedBlock(handler: (content: ReceivedBlock) => void | Promise); + public abstract onComplete( + handler: (startBlock?: bigint, endBlock?: bigint) => void | Promise + ); + public abstract onError(handler: (error: Error) => void | Promise); + public abstract onWarning(handler: (...args: unknown[]) => void | Promise); +} + +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + +export class BlockReaderService implements BlockReader { + private errorHandler: (error: Error) => void; + private warningHandler: (...args: unknown[]) => void; + private receivedBlockHandler: (content: ReceivedBlock) => Promise; + private blockRangeCompleteHandler: ( + startBlock: bigint, + endBlock: bigint + ) => Promise; + private blockRangeRequest: GetBlocksRequest; + private abi: Abi; + + constructor(private source: BlockReaderSource) { + this.source.onMessage(message => { + this.onMessage(message).catch((error: Error) => { + this.handleError(error); + }); + }); + this.source.onError(error => { + this.handleError(error); + }); + this.source.addConnectionStateHandler(BlockReaderConnectionState.Connected, options => + this.onConnected(options) + ); + this.source.addConnectionStateHandler(BlockReaderConnectionState.Idle, options => + this.onDisconnected(options) + ); + } + + public onConnected({ data }: ConnectionChangeHandlerOptions) { + log(`BlockReader plugin connected`); + + this.abi = Abi.fromDto(JSON.parse(data) as AbiDto); + } + + public onDisconnected({ previousState }: ConnectionChangeHandlerOptions) { + log(`BlockReader plugin disconnected`); + if (previousState === BlockReaderConnectionState.Disconnecting) { + this.abi = null; + } + } + + public async onMessage(dto: Uint8Array): Promise { + const { abi } = this; + + if (!abi) { + this.handleError(new AbiNotFoundError()); + return; + } + + const message = BlockReaderMessage.create(dto, abi.getTypesMap()); + + if (message.isGetStatusResult) { + // TODO: ? + } else if (message.isGetBlocksResult) { + await this.handleBlocksResultContent(message.content); + } else { + this.handleError(new UnhandledMessageTypeError(message.type)); + } + } + + private async handleBlocksResultContent(result: ReceivedBlock) { + const { + blockRangeRequest: { startBlock, endBlock }, + } = this; + const { thisBlock } = result; + const { abi } = this; + + if (!abi) { + this.handleError(new AbiNotFoundError()); + return; + } + + try { + if (thisBlock) { + const isLast = thisBlock.blockNumber === endBlock - 1n; + await this.receivedBlockHandler(result); + + // If received block is the last one call onComplete handler + if (isLast) { + this.blockRangeRequest = null; + await this.blockRangeCompleteHandler(startBlock, endBlock); + } + } else { + this.handleWarning(`the received message does not contain this_block`); + } + // State history plugs will answer every call of ack_request, even after + // processing the full range, it will send messages containing only head. + // After the block has been processed, the connection should be closed so + // there is no need to ack request. + if (this.source.isConnected) { + // Acknowledge a request so that source can send next one. + this.source.send(new GetBlocksAckRequest(1, abi.getTypesMap()).toUint8Array()); + } + } catch (error) { + this.handleError(new UnhandledMessageError(result, error)); + } + } + + private handleError(error: Error) { + if (this.errorHandler) { + return this.errorHandler(error); + } + } + + private handleWarning(...args: unknown[]) { + if (this.warningHandler) { + return this.warningHandler(...args); + } + } + + public async connect(): Promise { + if (!this.receivedBlockHandler || !this.blockRangeCompleteHandler) { + throw new MissingHandlersError(); + } + + if (!this.source.isConnected) { + log(`BlockReader plugin connecting...`); + await this.source.connect(); + } else { + log(`Service already connected`); + } + } + + public async disconnect(): Promise { + if (this.source.isConnected) { + log(`BlockReader plugin disconnecting...`); + await this.source.disconnect(); + } else { + log(`Service not connected`); + } + } + + public readBlocks( + startBlock: bigint, + endBlock: bigint, + options: BlockReaderOptions + ): void { + log(`BlockReader plugin trying to request blocks`); + // still processing block range request? + if (this.blockRangeRequest) { + throw new UnhandledBlockRequestError(startBlock, endBlock); + } + + const { abi } = this; + + if (!abi) { + throw new AbiNotFoundError(); + } + + this.blockRangeRequest = GetBlocksRequest.create( + startBlock, + endBlock, + options, + abi.getTypesMap() + ); + this.source.send(this.blockRangeRequest.toUint8Array()); + log(`BlockReader plugin request sent`); + } + + public onReceivedBlock(handler: (content: ReceivedBlock) => Promise) { + this.receivedBlockHandler = handler; + } + + public onComplete(handler: (startBlock: bigint, endBlock: bigint) => Promise) { + this.blockRangeCompleteHandler = handler; + } + + public onError(handler: (error: Error) => void) { + this.errorHandler = handler; + } + + public onWarning(handler: (...args: unknown[]) => void) { + this.warningHandler = handler; + } +} diff --git a/src/common/blockchain/block-reader/block-reader.types.ts b/src/common/blockchain/block-reader/block-reader.types.ts new file mode 100644 index 0000000..3948722 --- /dev/null +++ b/src/common/blockchain/block-reader/block-reader.types.ts @@ -0,0 +1,16 @@ +import { BlockReaderConnectionState } from './block-reader.enums'; + +export type ConnectionChangeHandlerOptions = { + previousState: BlockReaderConnectionState; + state: BlockReaderConnectionState; + data: string; +}; + +export type ConnectionChangeHandler = ( + options: ConnectionChangeHandlerOptions +) => void | Promise; + +export type BlockReaderOptions = { + shouldFetchDeltas?: boolean; + shouldFetchTraces?: boolean; +}; diff --git a/src/common/blockchain/block-reader/block-reader.utils.ts b/src/common/blockchain/block-reader/block-reader.utils.ts new file mode 100644 index 0000000..7d3d9d8 --- /dev/null +++ b/src/common/blockchain/block-reader/block-reader.utils.ts @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { log } from '@alien-worlds/api-core'; +import { Serialize } from 'eosjs'; +import { TextDecoder, TextEncoder } from 'text-encoding'; +import { BlockReader, BlockReaderService } from './block-reader'; +import { BlockReaderConfig } from './block-reader.config'; +import { BlockReaderSource } from './block-reader.source'; + +export const setupBlockReader = async ( + config: BlockReaderConfig +): Promise => { + const source = new BlockReaderSource(config); + source.onError(error => log(error)); + + await source.connect(); + + const blockReader = new BlockReaderService(source); + + return blockReader; +}; + +export const serializeMessage = ( + type: string, + value: unknown, + types: Map +) => { + const buffer = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + }); + Serialize.getType(types, type).serialize(buffer, value); + return buffer.asUint8Array(); +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const deserializeMessage = ( + type: string, + array: Uint8Array, + types: Map +): T => { + const buffer = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + array, + }); + const result = Serialize.getType(types, type).deserialize( + buffer, + new Serialize.SerializerState({ bytesAsUint8Array: true }) + ); + + if (buffer.readPos != array.length) throw new Error('oops: ' + type); // todo: remove check + return result; +}; diff --git a/src/common/blockchain/block-reader/index.ts b/src/common/blockchain/block-reader/index.ts new file mode 100644 index 0000000..69ac401 --- /dev/null +++ b/src/common/blockchain/block-reader/index.ts @@ -0,0 +1,9 @@ +export * from './block-reader'; +export * from './block-reader.config'; +export * from './block-reader.dtos'; +export * from './block-reader.enums'; +export * from './block-reader.errors'; +export * from './block-reader.source'; +export * from './block-reader.types'; +export * from './block-reader.utils'; +export * from './models'; diff --git a/src/common/blockchain/block-reader/models/__tests__/block-reader.message.unit.test.ts b/src/common/blockchain/block-reader/models/__tests__/block-reader.message.unit.test.ts new file mode 100644 index 0000000..cf5106e --- /dev/null +++ b/src/common/blockchain/block-reader/models/__tests__/block-reader.message.unit.test.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { ReceivedBlock } from '../received-block'; +import { BlockReaderMessage } from '../block-reader.message'; +import { deserializeMessage } from '../../block-reader.utils'; + +jest.mock('eosjs/dist/eosjs-serialize'); +jest.mock( + '../../state-history.utils', + jest.fn(() => ({ + deserializeMessage: jest.fn(() => ['get_status_result_v0', {}]), + })) +); + +describe('BlockReaderMessage Unit tests', () => { + it('"create" should create BlockReaderMessage entity based on given DTO', async () => { + ReceivedBlock.create = jest.fn().mockImplementation(); + + const entity = BlockReaderMessage.create(Uint8Array.from([]), new Map()); + + expect(entity).toBeInstanceOf(BlockReaderMessage); + expect(deserializeMessage).toBeCalled(); + }); + + it('"isGetStatusResult" should return true when type is "get_status_result_{version}"', async () => { + ReceivedBlock.create = jest.fn().mockImplementation(); + + const entity = BlockReaderMessage.create(Uint8Array.from([]), new Map()); + + expect(entity.isGetStatusResult).toBeTruthy(); + }); + + it('"isGetBlocksResult" should return true when type is "get_blocks_result_{version}"', async () => { + ReceivedBlock.create = jest.fn().mockImplementation(); + + const entity = BlockReaderMessage.create(Uint8Array.from([]), new Map()); + (entity as any).type = `get_blocks_result_${entity.version}`; + + expect(entity.isGetBlocksResult).toBeTruthy(); + }); +}); diff --git a/src/common/blockchain/block-reader/models/__tests__/get-blocks-ack.request.unit.test.ts b/src/common/blockchain/block-reader/models/__tests__/get-blocks-ack.request.unit.test.ts new file mode 100644 index 0000000..8f950a8 --- /dev/null +++ b/src/common/blockchain/block-reader/models/__tests__/get-blocks-ack.request.unit.test.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { serializeMessage } from '../../block-reader.utils'; +import { GetBlocksAckRequest } from '../get-blocks-ack.request'; + +jest.mock('eosjs/dist/eosjs-serialize'); +jest.mock('../../state-history.utils'); + +describe('GetBlocksAckRequest Unit tests', () => { + it('"create" should create GetBlocksAckRequest entity based on given DTO', async () => { + const entity = new GetBlocksAckRequest(1, new Map()); + expect(entity).toBeInstanceOf(GetBlocksAckRequest); + }); + + it('"toUint8Array" should call serializeMessage util', async () => { + const entity = new GetBlocksAckRequest(1, new Map()); + entity.toUint8Array(); + expect(serializeMessage).toBeCalled(); + }); +}); diff --git a/src/common/blockchain/block-reader/models/__tests__/get-blocks.request.unit.test.ts b/src/common/blockchain/block-reader/models/__tests__/get-blocks.request.unit.test.ts new file mode 100644 index 0000000..7e1b751 --- /dev/null +++ b/src/common/blockchain/block-reader/models/__tests__/get-blocks.request.unit.test.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { serializeMessage } from '../../block-reader.utils'; +import { GetBlocksRequest } from '../get-blocks.request'; + +jest.mock('eosjs/dist/eosjs-serialize'); +jest.mock('../../state-history.utils'); + +describe('GetBlocksRequest Unit tests', () => { + it('"create" should create GetBlocksRequest entity based on given DTO', async () => { + const entity = GetBlocksRequest.create( + 0n, 1n, + { + shouldFetchDeltas: true, + shouldFetchTraces: true, + }, + new Map() + ); + expect(entity).toBeInstanceOf(GetBlocksRequest); + }); + + it('"toUint8Array" should call serializeMessage util', async () => { + const entity = GetBlocksRequest.create( + 0n, 1n, + { + shouldFetchDeltas: true, + shouldFetchTraces: true, + }, + new Map() + ); + entity.toUint8Array(); + expect(serializeMessage).toBeCalled(); + }); +}); diff --git a/src/common/blockchain/block-reader/models/__tests__/get-blocks.result.unit.test.ts b/src/common/blockchain/block-reader/models/__tests__/get-blocks.result.unit.test.ts new file mode 100644 index 0000000..46452d7 --- /dev/null +++ b/src/common/blockchain/block-reader/models/__tests__/get-blocks.result.unit.test.ts @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Block, Delta, Trace } from '../../../block-content'; +import { deserializeMessage } from '../../block-reader.utils'; +import { ReceivedBlock } from '../received-block'; + +jest.mock('eosjs/dist/eosjs-serialize'); +jest.mock( + '../../state-history.utils', + jest.fn(() => ({ + deserializeMessage: jest.fn(() => []), + })) +); + +const dto = { + head: { + block_num: 1000, + block_id: '1000', + }, + this_block: { + block_num: 1000, + block_id: '1000', + }, + last_irreversible: { + block_num: 1000, + block_id: '1000', + }, + prev_block: { + block_num: 1000, + block_id: '1000', + }, + block: Uint8Array.from([]), + traces: Uint8Array.from([]), + deltas: Uint8Array.from([]), +}; + +describe('GetBlocksResult Unit tests', () => { + it('"create" should create GetBlocksResult entity based on given DTO', async () => { + const entity = ReceivedBlock.create(dto, new Map()); + expect(entity).toBeInstanceOf(ReceivedBlock); + }); + + it('"create" should call deserializeMessage when result contains block or deltas or traces', async () => { + dto.block = Uint8Array.from([1, 2, 3, 4]); + dto.traces = Uint8Array.from([1, 2, 3, 4]); + dto.deltas = Uint8Array.from([1, 2, 3, 4]); + Block.create = jest.fn().mockReturnValue({}); + Trace.create = jest.fn().mockImplementation(); + Delta.create = jest.fn().mockImplementation(); + ReceivedBlock.create(dto, new Map()); + expect(deserializeMessage).toBeCalledTimes(3); + }); +}); diff --git a/src/common/blockchain/block-reader/models/block-reader.message.ts b/src/common/blockchain/block-reader/models/block-reader.message.ts new file mode 100644 index 0000000..4f3e3a4 --- /dev/null +++ b/src/common/blockchain/block-reader/models/block-reader.message.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import { Serialize } from 'eosjs'; +import { GetBlocksResultDto } from '../block-reader.dtos'; +import { deserializeMessage } from '../block-reader.utils'; +import { ReceivedBlock } from './received-block'; + +export class BlockReaderMessage { + public readonly version = 'v0'; + + public static create(dto: Uint8Array, types: Map) { + const result = deserializeMessage('result', dto, types); + let content: ReceivedBlock; + let type: string; + + if (result) { + const [resultType, contentDto]: [string, GetBlocksResultDto] = result; + content = ReceivedBlock.create(contentDto, types); + type = resultType; + } + + return new BlockReaderMessage(type, content); + } + + private constructor( + public readonly type: string, + public readonly content: MessageContentType + ) {} + + get isGetStatusResult() { + return this.type === `get_status_result_${this.version}`; + } + + get isGetBlocksResult() { + return this.type === `get_blocks_result_${this.version}`; + } +} diff --git a/src/common/blockchain/block-reader/models/get-blocks-ack.request.ts b/src/common/blockchain/block-reader/models/get-blocks-ack.request.ts new file mode 100644 index 0000000..13e92d0 --- /dev/null +++ b/src/common/blockchain/block-reader/models/get-blocks-ack.request.ts @@ -0,0 +1,19 @@ +import { Serialize } from 'eosjs'; +import { serializeMessage } from '../block-reader.utils'; + +export class GetBlocksAckRequest { + public readonly version = 'v0'; + + constructor( + public readonly messagesCount: number, + public readonly types: Map + ) {} + + public toUint8Array() { + return serializeMessage( + 'request', + [`get_blocks_ack_request_${this.version}`, { num_messages: this.messagesCount }], + this.types + ); + } +} diff --git a/src/common/blockchain/block-reader/models/get-blocks.request.ts b/src/common/blockchain/block-reader/models/get-blocks.request.ts new file mode 100644 index 0000000..49cbcea --- /dev/null +++ b/src/common/blockchain/block-reader/models/get-blocks.request.ts @@ -0,0 +1,52 @@ +import { Serialize } from 'eosjs'; +import { BlockReaderOptions } from '../block-reader.types'; +import { serializeMessage } from '../block-reader.utils'; + +export class GetBlocksRequest { + public readonly version = 'v0'; + + public static create( + startBlock: bigint, + endBlock: bigint, + options: BlockReaderOptions, + types: Map + ) { + const { shouldFetchDeltas, shouldFetchTraces } = options; + + return new GetBlocksRequest( + startBlock, + endBlock, + shouldFetchTraces, + shouldFetchDeltas, + types + ); + } + + private constructor( + public readonly startBlock: bigint, + public readonly endBlock: bigint, + public readonly shouldFetchTraces: boolean, + public readonly shouldFetchDeltas: boolean, + public readonly types: Map + ) {} + + public toUint8Array(): Uint8Array { + return serializeMessage( + 'request', + [ + `get_blocks_request_${this.version}`, + { + irreversible_only: false, + start_block_num: Number(this.startBlock.toString()), + end_block_num: Number(this.endBlock.toString()), + max_messages_in_flight: 1, + have_positions: [], + fetch_block: true, + fetch_traces: this.shouldFetchTraces, + fetch_deltas: this.shouldFetchDeltas, + }, + ], + this.types + ); + } +} diff --git a/src/common/blockchain/block-reader/models/index.ts b/src/common/blockchain/block-reader/models/index.ts new file mode 100644 index 0000000..6ed2875 --- /dev/null +++ b/src/common/blockchain/block-reader/models/index.ts @@ -0,0 +1,4 @@ +export * from './block-reader.message'; +export * from './get-blocks-ack.request'; +export * from './get-blocks.request'; +export * from './received-block'; diff --git a/src/common/blockchain/block-reader/models/received-block.ts b/src/common/blockchain/block-reader/models/received-block.ts new file mode 100644 index 0000000..df68cee --- /dev/null +++ b/src/common/blockchain/block-reader/models/received-block.ts @@ -0,0 +1,75 @@ +import { parseToBigInt } from '@alien-worlds/api-core'; +import { Serialize } from 'eosjs'; +import { Block, BlockDto } from '../../block-content/block'; +import { Delta, DeltaDto } from '../../block-content/delta'; +import { Trace, TraceDto } from '../../block-content/trace'; +import { BlockNumberWithIdDto, GetBlocksResultDto } from '../block-reader.dtos'; +import { deserializeMessage } from '../block-reader.utils'; + +export class BlockNumberWithId { + public static create(dto: BlockNumberWithIdDto) { + const { block_id, block_num } = dto; + return new BlockNumberWithId(parseToBigInt(block_num), block_id); + } + + private constructor( + public readonly blockNumber: bigint, + public readonly blockId: string + ) {} +} + +export class ReceivedBlock { + public static create(content: GetBlocksResultDto, types: Map): ReceivedBlock { + const { head, last_irreversible, prev_block, this_block } = content; + let block: Block; + let traces: Trace[] = []; + let deltas: Delta[] = []; + + if (content.block && content.block.length > 0) { + const deserializedBlock = deserializeMessage( + 'signed_block', + content.block, + types + ); + block = Block.create(deserializedBlock); + } + + if (content.traces && content.traces.length > 0) { + const tracesByType = deserializeMessage<[[string, TraceDto]]>( + 'transaction_trace[]', + content.traces, + types + ); + traces = tracesByType.map(([type, traceDto]) => Trace.create(type, traceDto)); + } + + if (content.deltas && content.deltas.length > 0) { + const deltasByType = deserializeMessage<[[string, DeltaDto]]>( + 'table_delta[]', + content.deltas, + types + ); + deltas = deltasByType.map(([type, deltaDto]) => Delta.create(type, deltaDto)); + } + + return new ReceivedBlock( + BlockNumberWithId.create(head), + BlockNumberWithId.create(this_block), + BlockNumberWithId.create(prev_block), + BlockNumberWithId.create(last_irreversible), + block, + traces, + deltas + ); + } + + private constructor( + public readonly head: BlockNumberWithId, + public readonly thisBlock: BlockNumberWithId, + public readonly prevBlock: BlockNumberWithId, + public readonly lastIrreversible: BlockNumberWithId, + public readonly block: Block, + public readonly traces: Trace[], + public readonly deltas: Delta[] + ) {} +} diff --git a/src/common/blockchain/blockchain.utils.ts b/src/common/blockchain/blockchain.utils.ts new file mode 100644 index 0000000..4940592 --- /dev/null +++ b/src/common/blockchain/blockchain.utils.ts @@ -0,0 +1,19 @@ +import { parseToBigInt } from '@alien-worlds/api-core'; +import { Api, JsonRpc } from 'eosjs'; +import fetch from 'node-fetch'; + +export const getLastIrreversibleBlockNumber = async ( + endpoint: string, + chainId: string +): Promise => { + const api = new Api({ + rpc: new JsonRpc(endpoint, { fetch }), + chainId, + signatureProvider: null, + textDecoder: new TextDecoder(), + textEncoder: new TextEncoder(), + }); + + const info = await api.rpc.get_info(); + return parseToBigInt(info.last_irreversible_block_num); +}; diff --git a/src/common/blockchain/index.ts b/src/common/blockchain/index.ts new file mode 100644 index 0000000..1a5aab7 --- /dev/null +++ b/src/common/blockchain/index.ts @@ -0,0 +1 @@ +export * from './blockchain.utils'; diff --git a/src/common/broadcast/amq/broadcast.amq.client.ts b/src/common/broadcast/amq/broadcast.amq.client.ts new file mode 100644 index 0000000..ea92a5c --- /dev/null +++ b/src/common/broadcast/amq/broadcast.amq.client.ts @@ -0,0 +1,372 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +import * as Amq from 'amqplib'; +import { MapperNotFoundError } from '../broadcast.errors'; +import { + ConnectionState, + Broadcast, + BroadcastMessage, + BroadcastOptions, +} from '../broadcast.types'; +import { wait } from '../broadcast.utils'; + +import { ConnectionStateHandler, MessageHandler } from '../broadcast.types'; +import { log } from '@alien-worlds/api-core'; +import { BroadcastAmqMessage } from './broadcast.amq.message'; + +export type Ack = (message: Amq.Message) => void; +export type Reject = (message: Amq.Message, requeue: boolean) => void; + +/** + * @class + */ +export class BroadcastAmqClient implements Broadcast { + private channel: Amq.Channel; + private connection: Amq.Connection; + private connectionErrorsCount: number; + private maxConnectionErrors: number; + private handlers: Map[]>; + private connectionStateHandlers: Map; + private initialized: boolean; + private connectionState: ConnectionState; + private connectionError: unknown; + + /** + * @constructor + * @param {string} address - connection string + * @param {ChannelOptions} channelOptions - channel options + * @param {Console} logger - logger instance + */ + constructor( + private address: string, + public channelOptions: BroadcastOptions, + private logger: Console + ) { + this.initialized = false; + this.handlers = new Map[]>(); + this.connectionStateHandlers = new Map(); + this.connectionErrorsCount = 0; + this.connectionState = ConnectionState.Offline; + this.maxConnectionErrors = 5; + } + /** + * Reconnect to server + * + * This function is called when the connection is closed. + * + * @private + * @async + */ + private async handleConnectionClose(): Promise { + if (this.connectionState === ConnectionState.Closing) { + this.connectionState = ConnectionState.Offline; + this.logger.warn('Connection closed'); + + if (this.connectionStateHandlers.has(ConnectionState.Offline)) { + await this.connectionStateHandlers.get(ConnectionState.Offline)(); + } + + await this.reconnect(); + } + } + + /** + * Logs a connection error and tries to reconnect. + * This function is called when there is a connection error. + * + * @private + * @async + * @param {Error} error + */ + private handleConnectionError(error: Error): void { + if (error.message !== 'Connection closing') { + this.connectionErrorsCount++; + if (this.connectionErrorsCount > this.maxConnectionErrors) { + this.logger.error('Connection Error', { e: error }); + } else { + this.logger.warn('Connection Error', { e: error }); + } + } + } + + private async handleChannelCancel(reason) { + if (this.connectionState === ConnectionState.Online) { + await this.close(reason); + } + } + + private async handleChannelClose() { + if (this.connectionState === ConnectionState.Online) { + await this.close(); + } + } + + private async handleChannelError(error) { + if (this.connectionState === ConnectionState.Online) { + await this.close(error); + } + } + + /** + * Reconnect to server and reassign queue handlers. + * This function is called when the connection is lost + * due to an error or closure. + * + * After a failed connection attempt, the function calls + * itself after a specified time. + * + * @private + * @async + */ + private async reconnect() { + if (this.connectionState === ConnectionState.Offline) { + this.initialized = false; + log(`Reloading connection with handlers`); + + try { + await this.init(); + await this.reassignHandlers(); + } catch (error) { + this.connectionState = ConnectionState.Offline; + this.connectionErrorsCount++; + const ms = Math.pow(this.connectionErrorsCount, 2) * 1000; + await this.waitAndReconnect(ms); + } + } + } + + /** + * Wait for the specified time and reconnect. + * (Written to facilitate unit testing) + * + * @param {number} ms + */ + private async waitAndReconnect(ms: number) { + await wait(ms); + await this.reconnect(); + } + + /** + * Reassign queue handlers stored in the 'handlers' map. + * This function is called when the connection is restored + * + * @private + * @async + */ + private async reassignHandlers(): Promise { + const promises = []; + this.handlers.forEach((handlers: MessageHandler[], queue: string) => { + handlers.forEach(handler => + promises.push( + this.channel.consume(queue, message => handler(message), { + noAck: false, + }) + ) + ); + }); + await Promise.all(promises); + } + + /** + * Create channel and set up queues. + * + * @private + * @async + */ + private async createChannel(): Promise { + const { prefetch, queues } = this.channelOptions; + this.channel = await this.connection.createChannel(); + this.channel.on('cancel', async data => this.handleChannelCancel(data)); + this.channel.on('close', async () => this.handleChannelClose()); + this.channel.on('error', async error => this.handleChannelError(error)); + log(`Channel created.`); + + await this.channel.prefetch(prefetch); + for (const queue of queues) { + await this.channel.assertQueue(queue.name, queue.options); + } + + log(`Queues set up.`); + } + + /** + * Connect to server + * + * @private + * @async + */ + private async connect(): Promise { + if (this.connectionState !== ConnectionState.Offline) { + return; + } + this.connectionState = ConnectionState.Connecting; + this.connection = await Amq.connect(this.address); + this.connection.on('error', (error: Error) => { + this.handleConnectionError(error); + }); + this.connection.on('close', async () => { + await this.handleConnectionClose(); + }); + this.connectionState = ConnectionState.Online; + + log(`Connected to AMQ ${this.address}`); + } + + /** + * Close connection + * + * @param {unknown} reason + */ + public async close(reason?: unknown): Promise { + if (this.connectionState === ConnectionState.Online) { + this.connectionState = ConnectionState.Closing; + if (reason) { + this.connectionError = reason; + } + await this.connection.close(); + + log(`Disconnected from AMQ ${this.address}`); + } + } + + /** + * Initialize driver + * + * @async + */ + public async init(): Promise { + if (!this.initialized) { + await this.connect(); + await this.createChannel(); + + this.initialized = true; + this.connectionErrorsCount = 0; + } + } + + /** + * Send a single message with the content given as a buffer to the specific queue named, bypassing routing. + * + * @async + * @param {string} queue + * @param {Buffer} message + */ + public async sendMessage(name: string, message: unknown): Promise { + return new Promise((resolve, reject) => { + const { mapper } = + this.channelOptions.queues.find(queue => queue.name === name) || {}; + + if (mapper) { + const success = this.channel.sendToQueue(name, mapper.toSource(message), { + deliveryMode: true, + }); + return success ? resolve() : reject(); + } + + return reject(new MapperNotFoundError(name)); + }); + } + /** + * Set up a listener for the queue. + * + * @param {string} queue - queue name + * @param {MessageHandler} handler - queue handler + */ + public onMessage(name: string, handler: MessageHandler): void { + try { + const { mapper, noAck } = + this.channelOptions.queues.find(queue => queue.name === name) || {}; + + if (mapper) { + if (this.handlers.has(name)) { + this.handlers.get(name).push(handler); + } else { + this.handlers.set(name, [handler]); + } + this.channel.consume( + name, + (message: Amq.Message) => { + const { + properties: { messageId }, + content, + } = message; + const data = mapper.toContent(content); + handler( + new BroadcastAmqMessage( + messageId, + data, + message, + this.channel.ack, + this.channel.reject + ) + ); + }, + { + noAck, + } + ); + } else { + throw new MapperNotFoundError(name); + } + } catch (error) { + this.logger.error(`Failed to add listener`, error); + } + } + + /** + * Acknowledge the message. + * + * @param {Message} message + */ + public ack(message: Amq.Message): void { + try { + this.channel.ack(message); + } catch (error) { + this.logger.error(`Failed to ack message`, error); + } + } + + /** + * Reject a message. + * Negative acknowledgement - set a message as not delivered and should be discarded. + * + * @param {Message} message + */ + public reject(message: Amq.Message, requeue = true): void { + try { + this.channel.reject(message, requeue); + } catch (error) { + this.logger.error(`Failed to reject message`, error); + } + } + + /** + * + * @param {ConnectionState} state + * @param {ConnectionStateHandler} handler + */ + public addConnectionStateHandler( + state: ConnectionState, + handler: ConnectionStateHandler + ): void { + if (this.connectionStateHandlers.has(state)) { + this.logger.warn(`Overwriting connection state: ${state} handler`); + } + this.connectionStateHandlers.set(state, handler); + } + + /** + * + * @param {ConnectionState} state + */ + public removeConnectionStateHandlers(state?: ConnectionState): void { + if (state) { + this.connectionStateHandlers.delete(state); + } else { + this.connectionStateHandlers.clear(); + } + } +} diff --git a/src/common/broadcast/amq/broadcast.amq.message.ts b/src/common/broadcast/amq/broadcast.amq.message.ts new file mode 100644 index 0000000..de698f3 --- /dev/null +++ b/src/common/broadcast/amq/broadcast.amq.message.ts @@ -0,0 +1,32 @@ +import * as Amq from 'amqplib'; +import { BroadcastMessage } from '../broadcast.types'; + +export class BroadcastAmqMessage + implements BroadcastMessage +{ + /** + * @constructor + * @param {string} id + * @param {ContentType} content + * @param {SourceType} _source + */ + constructor( + public readonly id: string, + public readonly content: ContentType, + private readonly _source: Amq.Message, + private readonly _ack: (source: Amq.Message) => void, + private readonly _reject: (source: Amq.Message, requeue: boolean) => void + ) {} + + public ack(): void { + return this._ack(this._source); + } + + public reject(): void { + return this._reject(this._source, false); + } + + public postpone(): void { + return this._reject(this._source, true); + } +} diff --git a/src/common/broadcast/broadcast.errors.ts b/src/common/broadcast/broadcast.errors.ts new file mode 100644 index 0000000..3bc71cb --- /dev/null +++ b/src/common/broadcast/broadcast.errors.ts @@ -0,0 +1,5 @@ +export class MapperNotFoundError extends Error { + constructor(queue: string) { + super(`Mapper not found for ${queue}`); + } +} diff --git a/src/common/broadcast/broadcast.types.ts b/src/common/broadcast/broadcast.types.ts new file mode 100644 index 0000000..50430e9 --- /dev/null +++ b/src/common/broadcast/broadcast.types.ts @@ -0,0 +1,51 @@ +export enum ConnectionState { + Online = 'online', + Offline = 'offline', + Connecting = 'connecting', + Closing = 'closing', +} + +export type QueueOptions = { + name: string; + options: { durable: boolean }; + mapper: BroadcastMessageContentMapper; + noAck: boolean; +}; + +export type BroadcastOptions = { + prefetch: number; + queues: QueueOptions[]; +}; + +export type MessageHandler = ( + message: BroadcastMessageType +) => void; +export type ConnectionStateHandler = (...args: unknown[]) => Promise; + +export abstract class BroadcastMessage { + public id: string; + public content: ContentType; + public abstract ack(): void; + public abstract reject(): void; + public abstract postpone(): void; +} + +export abstract class BroadcastMessageContentMapper< + ContentType = unknown, + SourceType = unknown +> { + public abstract toContent(source: SourceType): ContentType; + public abstract toSource(content: ContentType): SourceType; +} + +/** + * @abstract + * @class + */ +export abstract class Broadcast { + public abstract sendMessage(channel: string, data: unknown): Promise; + public abstract onMessage( + channel: string, + handler: (message: BroadcastMessage) => void + ): void; +} diff --git a/src/common/broadcast/broadcast.utils.ts b/src/common/broadcast/broadcast.utils.ts new file mode 100644 index 0000000..06dce29 --- /dev/null +++ b/src/common/broadcast/broadcast.utils.ts @@ -0,0 +1,27 @@ +import { BroadcastAmqClient } from './amq/broadcast.amq.client'; +import { Broadcast, BroadcastOptions } from './broadcast.types'; + +/** + * Suspends execution of the current process for a given number of milliseconds + * @async + * @param {number} ms + * @returns {Promise} + */ +export const wait = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +/** + * + * @param {ProcessorConfig} config + * @param {BroadcastMessageMapper} broadcastMessageMapper + * @returns { Broadcast } + */ +export const setupBroadcast = async ( + url: string, + options: BroadcastOptions +): Promise => { + const broadcastClient = new BroadcastAmqClient(url, options, console); + + await broadcastClient.init(); + + return broadcastClient as BroadcastType; +}; diff --git a/src/common/broadcast/index.ts b/src/common/broadcast/index.ts new file mode 100644 index 0000000..cb17fb8 --- /dev/null +++ b/src/common/broadcast/index.ts @@ -0,0 +1,7 @@ +export * from './broadcast.utils'; +export * from './broadcast.errors'; +export * from './broadcast.types'; +// AMQ +export * from './amq/broadcast.amq.client'; +export * from './amq/broadcast.amq.message'; +// REDIS diff --git a/src/common/enums.ts b/src/common/enums.ts new file mode 100644 index 0000000..54f5559 --- /dev/null +++ b/src/common/enums.ts @@ -0,0 +1,5 @@ +export enum Mode { + default = 'defualt', + replay = 'replay', + test = 'test', +} diff --git a/src/common/workers/index.ts b/src/common/workers/index.ts new file mode 100644 index 0000000..10650dd --- /dev/null +++ b/src/common/workers/index.ts @@ -0,0 +1,6 @@ +export * from './worker-message'; +export * from './worker-pool'; +export * from './worker-task'; +export * from './worker.enums'; +export * from './worker.errors'; +export * from './worker.utils'; diff --git a/src/common/workers/worker-message.ts b/src/common/workers/worker-message.ts new file mode 100644 index 0000000..3583916 --- /dev/null +++ b/src/common/workers/worker-message.ts @@ -0,0 +1,106 @@ +export type WorkerMessageOptions = { + pid: number; + type: string; + name: string; + content?: ContentType; + error?: Error; +}; + +export type WorkerMessageHandler = (message: WorkerMessage) => void; + +export class WorkerMessage { + public static create({ + pid, + type, + name, + content, + error, + }: WorkerMessageOptions) { + let errorJson: Error; + if (error) { + const { message, stack, name: errorName, ...rest } = error; + errorJson = { + message, + stack, + name: errorName, + ...rest, + }; + } + + return new WorkerMessage(pid, type, name, content, errorJson); + } + + public static runTask(pid: number, value: ContentType) { + return new WorkerMessage( + pid, + WorkerMessageType.Info, + WorkerMessageName.TaskResolved, + value + ); + } + + public static taskResolved(pid: number, value: ContentType) { + return new WorkerMessage( + pid, + WorkerMessageType.Info, + WorkerMessageName.TaskResolved, + value + ); + } + + public static taskRejected(pid: number, error: Error) { + return new WorkerMessage( + pid, + WorkerMessageType.Error, + WorkerMessageName.TaskRejected, + null, + error + ); + } + + private constructor( + public readonly pid: number, + public readonly type: string, + public readonly name: string, + public readonly content: ContentType, + public readonly error?: Error + ) {} + + public isTaskResolved(): boolean { + return this.name === WorkerMessageName.TaskResolved; + } + + public toJson(): object { + const { pid, type, name, content, error } = this; + let errorJson = {}; + if (error) { + const { message, stack, name: errorName, ...rest } = error; + errorJson = { + message, + stack, + name: errorName, + ...rest, + }; + } + return { + pid, + type, + name, + content, + error: errorJson, + }; + } +} + +export enum WorkerMessageType { + Error = 'error', + Info = 'info', + Warning = 'warning', + Task = 'task', +} + +export enum WorkerMessageName { + Start = 'start', + TaskResolved = 'task_resolved', + TaskRejected = 'task_rejected', +} diff --git a/src/common/workers/worker-pool.ts b/src/common/workers/worker-pool.ts new file mode 100644 index 0000000..4996949 --- /dev/null +++ b/src/common/workers/worker-pool.ts @@ -0,0 +1,100 @@ +import { log } from '@alien-worlds/api-core'; +import { Worker } from 'worker_threads'; +import { WorkerTask } from './worker-task'; +import { MissingWorkerTaskPathError, WorkerTaskPathMismatchError } from './worker.errors'; +import { getWorkersCount } from './worker.utils'; + +const workerRunnerPath = `${__dirname}/worker`; + +export type WorkerPoolOptions = { + threadsCount?: number; + inviolableThreadsCount?: number; + globalWorkerPath?: string; + sharedData?: unknown; +}; + +export class WorkerPool { + private workerMaxCount: number; + private globalWorkerPath: string; + private availableWorkers: WorkerTask[] = []; + private activeWorkersByPid = new Map(); + private sharedData: unknown; + + constructor(options: WorkerPoolOptions) { + const { threadsCount, inviolableThreadsCount, globalWorkerPath, sharedData } = + options; + this.globalWorkerPath = globalWorkerPath; + this.sharedData = sharedData; + this.workerMaxCount = getWorkersCount(threadsCount, inviolableThreadsCount); + + if (globalWorkerPath) { + for (let i = 0; i < this.workerMaxCount; i++) { + const worker = this.createWorker({ path: globalWorkerPath }); + this.availableWorkers.push(worker); + } + } + } + + public get workerCount() { + return this.availableWorkers.length + this.activeWorkersByPid.size; + } + + private createWorker(workerData: { + path: string; + }): WorkerTask { + return new WorkerTask( + new Worker(workerRunnerPath, { + workerData: { ...workerData, sharedData: this.sharedData }, + }) + ); + } + + public getWorker( + path?: string): WorkerTask { + const { globalWorkerPath, activeWorkersByPid, workerMaxCount, availableWorkers } = + this; + + if (!path && !globalWorkerPath) { + throw new MissingWorkerTaskPathError(); + } + + if (path && globalWorkerPath && path !== globalWorkerPath) { + throw new WorkerTaskPathMismatchError(path, globalWorkerPath); + } + + if (globalWorkerPath && activeWorkersByPid.size < workerMaxCount) { + const worker = availableWorkers.pop(); + activeWorkersByPid.set(worker.id, worker); + return worker; + } else if (path && activeWorkersByPid.size < workerMaxCount) { + const worker = this.createWorker({ path }); + activeWorkersByPid.set(worker.id, worker); + return worker; + } else { + return null; + } + } + + public async releaseWorker(id: number, remove?: boolean): Promise { + const { activeWorkersByPid, availableWorkers, workerMaxCount } = this; + const worker = activeWorkersByPid.get(id); + + if (worker && remove) { + const result = await worker.remove(); + if (result) { + this.activeWorkersByPid.delete(id); + } + } else if (worker && !remove) { + this.activeWorkersByPid.delete(id); + if (availableWorkers.length < workerMaxCount) { + availableWorkers.push(worker); + } + } else { + log(`No worker with the specified ID (${id}) was found`); + } + } + + public hasAvailableWorker(): boolean { + return this.availableWorkers.length > 0; + } +} diff --git a/src/common/workers/worker-task.ts b/src/common/workers/worker-task.ts new file mode 100644 index 0000000..ac43278 --- /dev/null +++ b/src/common/workers/worker-task.ts @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/require-await */ +import { log } from '@alien-worlds/api-core'; +import { parentPort, Worker } from 'worker_threads'; +import { WorkerMessage } from './worker-message'; + +export type TaskResolved = 'task_resolved'; +export type TaskRejected = 'task_rejected'; +export type TaskStatus = TaskResolved | TaskRejected; + +/** + * @class + */ +export class TaskResolver { + public static resolve(value?: unknown): TaskResolved { + parentPort.postMessage(WorkerMessage.taskResolved(process.pid, value)); + return 'task_resolved'; + } + public static reject(error?: Error): TaskRejected { + parentPort.postMessage(WorkerMessage.taskRejected(process.pid, error)); + return 'task_rejected'; + } +} + +export class WorkerTask { + constructor(private worker: Worker) {} + + public get id(): number { + return this.worker.threadId; + } + + public run(data: unknown): void { + const { worker } = this; + worker.postMessage(WorkerMessage.runTask(worker.threadId, data)); + } + + public onMessage(handler: (message: WorkerMessage) => Promise) { + this.worker.on('message', (message: WorkerMessage) => { + handler(message).catch(log); + }); + } + + public onError(handler: (error: Error) => void) { + this.worker.on('error', handler); + } + + public onExit(handler: (code: number) => void) { + this.worker.on('exit', handler); + } + + public async remove(): Promise { + const code = await this.worker.terminate(); + return !!code; + } +} diff --git a/src/common/workers/worker.enums.ts b/src/common/workers/worker.enums.ts new file mode 100644 index 0000000..4e87f41 --- /dev/null +++ b/src/common/workers/worker.enums.ts @@ -0,0 +1,4 @@ +export enum WorkerStatus { + complete = 'complete', + error = 'complete', +} \ No newline at end of file diff --git a/src/common/workers/worker.errors.ts b/src/common/workers/worker.errors.ts new file mode 100644 index 0000000..d8e5fdf --- /dev/null +++ b/src/common/workers/worker.errors.ts @@ -0,0 +1,13 @@ +export class MissingWorkerTaskPathError extends Error {} + +export class WorkerTaskPathMismatchError extends Error { + constructor(path: string, globalPath: string) { + super(`You cannot use path (${path}) when global is specified (${globalPath})`); + } +} + +export class WorkerNotFoundError extends Error { + constructor(id: number) { + super(`No worker with the specified ID (${id}) was found`); + } +} diff --git a/src/common/workers/worker.ts b/src/common/workers/worker.ts new file mode 100644 index 0000000..bc5ff62 --- /dev/null +++ b/src/common/workers/worker.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ +/* eslint-disable @typescript-eslint/restrict-template-expressions */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-var-requires */ + +/** + * Do not remove this file, it is required in WorkerPool + */ + +import path from 'path'; +import { workerData, parentPort } from 'worker_threads'; + +const { path: workerPath, sharedData } = workerData; +let workerFullPath; + +if (!workerPath) { + throw new Error(`Worker path not defined`); +} + +if (!workerPath.endsWith('.ts')) { + require('ts-node').register(); + workerFullPath = path.resolve(process.cwd(), 'src', `${workerPath}`); +} else { + workerFullPath = path.resolve(process.cwd(), 'build', `${workerPath}`); +} + +const { run } = require(workerFullPath); +if (run) { + parentPort.on('message', data => { + run(data, sharedData); + }); +} else { + throw new Error(`"run" function not found in: ${workerPath}`); +} diff --git a/src/common/workers/worker.utils.ts b/src/common/workers/worker.utils.ts new file mode 100644 index 0000000..92b9750 --- /dev/null +++ b/src/common/workers/worker.utils.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import * as os from 'os'; +import { workerData } from 'worker_threads'; +/** + * Get the number of workers from configuration + * or based on the number of available CPU cores. + * The number of CPU cores is reduced by + * a constant specified in the configuration. + * + * @param {Config} config + * @returns {number} + */ +export const getWorkersCount = (threadsCount: number, inviolableThreadsCount = 0) => { + if (threadsCount === 0 || isNaN(threadsCount)) { + const cpus = os.cpus().length; + return cpus - inviolableThreadsCount; + } + + return threadsCount; +}; + +export const getSharedData = () => workerData.sharedData as T; \ No newline at end of file diff --git a/src/filler/filler.config.ts b/src/filler/filler.config.ts new file mode 100644 index 0000000..9c6682e --- /dev/null +++ b/src/filler/filler.config.ts @@ -0,0 +1,19 @@ +import { BlockRangeScanConfig } from '../common/block-range-scanner'; + +export type FillerConfig = { + broadcast: { + url: string; + channel: string; + }; + blockchain: { + endpoint: string; + chainId: string; + }; + scanner: BlockRangeScanConfig; + mongo: { url: string; dbName: string }; + startBlock: bigint; + endBlock: bigint; + mode: string; + traces: string; + deltas: string; +}; diff --git a/src/filler/index.ts b/src/filler/index.ts new file mode 100644 index 0000000..537fa34 --- /dev/null +++ b/src/filler/index.ts @@ -0,0 +1,2 @@ +export * from './filler.config'; +export * from './start-filler'; diff --git a/src/filler/start-filler.ts b/src/filler/start-filler.ts new file mode 100644 index 0000000..96f4957 --- /dev/null +++ b/src/filler/start-filler.ts @@ -0,0 +1,182 @@ +import { log, parseToBigInt } from '@alien-worlds/api-core'; +import { + BlockRangeBroadcast, + createBlockRangeBroadcastOptions, +} from '../block-range/block-range.broadcast'; +import { BlockRangeTaskInput } from '../block-range/block-range.task-input'; +import { BlockRangeScanner, setupBlockRangeScanner } from '../common/block-range-scanner'; +import { BlockState, setupBlockState } from '../common/block-state'; +import { getLastIrreversibleBlockNumber } from '../common/blockchain'; +import { BroadcastMessageContentMapper, setupBroadcast } from '../common/broadcast'; +import { Mode } from '../common/enums'; +import { FillerConfig } from './filler.config'; + +/** + * + * @param {Broadcast} broadcast + * @param {FillerConfig} config + */ +export const startDefaultMode = async ( + broadcast: BlockRangeBroadcast, + blockState: BlockState, + config: FillerConfig +) => { + const { + startBlock, + endBlock, + mode, + traces, + deltas, + scanner: { scanKey }, + } = config; + + let highEdge: bigint; + let lowEdge: bigint; + + if (!startBlock) { + lowEdge = await blockState.getCurrentBlockNumber(); + } + + if (!endBlock) { + highEdge = parseToBigInt(0xffffffff); + } + + await broadcast.sendMessage( + BlockRangeTaskInput.create( + startBlock || lowEdge, + endBlock || highEdge, + mode, + scanKey, + traces, + deltas + ) + ); +}; + +/** + * + * @param {Broadcast} broadcast + * @param {FillerConfig} config + */ +export const startTestMode = async ( + broadcast: BlockRangeBroadcast, + config: FillerConfig +) => { + const { + startBlock, + endBlock, + mode, + scanner: { scanKey }, + traces, + deltas, + blockchain: { chainId, endpoint }, + } = config; + let highEdge: bigint; + + if (!startBlock) { + highEdge = await getLastIrreversibleBlockNumber(endpoint, chainId); + } + + await broadcast.sendMessage( + BlockRangeTaskInput.create( + startBlock || highEdge - 1n, + endBlock || highEdge, + mode, + scanKey, + traces, + deltas + ) + ); +}; + +/** + * + * @param {Broadcast} broadcast + * @param {FillerConfig} config + */ +export const startReplayMode = async ( + broadcast: BlockRangeBroadcast, + scanner: BlockRangeScanner, + config: FillerConfig +) => { + const { + blockchain: { chainId, endpoint }, + scanner: { scanKey }, + startBlock, + endBlock, + mode, + deltas, + traces, + } = config; + + let highEdge: bigint; + let lowEdge: bigint; + + if (!startBlock) { + lowEdge = await getLastIrreversibleBlockNumber(endpoint, chainId); + } + + if (!endBlock) { + highEdge = parseToBigInt(0xffffffff); + } + + // has it already (restarted replay) just send message + if (await scanner.hasUnscannedBlocks(scanKey, startBlock, endBlock)) { + log( + `Canceling a scan. There is already a block range (${startBlock.toString()}-${endBlock.toString()}) scan entry in the database with the selected key "${scanKey}". Please select a new unique key.` + ); + return; + } + + if (await scanner.createScanNodes(scanKey, startBlock, endBlock)) { + await broadcast.sendMessage( + BlockRangeTaskInput.create( + startBlock || lowEdge, + endBlock || highEdge, + mode, + scanKey, + traces, + deltas + ) + ); + } +}; + +/** + * + * @param broadcastMessageMapper + * @param config + * @returns + */ +export const startFiller = async ( + config: FillerConfig, + mapper?: BroadcastMessageContentMapper +) => { + const { + mode, + broadcast: { url }, + } = config; + const blockRangeBroadcastOptions = createBlockRangeBroadcastOptions(mapper); + const broadcast = await setupBroadcast( + url, + blockRangeBroadcastOptions + ); + + try { + if (mode === Mode.default) { + const blockState = await setupBlockState(config.mongo); + return startDefaultMode(broadcast, blockState, config); + } + + if (mode === Mode.replay) { + const scanner = await setupBlockRangeScanner(config.mongo, config.scanner); + return startReplayMode(broadcast, scanner, config); + } + + if (mode === Mode.test) { + return startTestMode(broadcast, config); + } + } catch (error) { + log(error); + } +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..27b90c9 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,9 @@ +export * from './processor'; +export * from './block-range'; +export * from './filler'; +export * from './common/block-range-scanner'; +export * from './common/block-state'; +export * from './common/blockchain'; +export * from './common/broadcast'; +export * from './common/workers'; +export * from './common/enums'; diff --git a/src/processor/index.ts b/src/processor/index.ts new file mode 100644 index 0000000..ba6e7a8 --- /dev/null +++ b/src/processor/index.ts @@ -0,0 +1,3 @@ +export * from './processor.config'; +export * from './processor.broadcast'; +export * from './start-processor'; diff --git a/src/processor/processor.broadcast.ts b/src/processor/processor.broadcast.ts new file mode 100644 index 0000000..c645f4d --- /dev/null +++ b/src/processor/processor.broadcast.ts @@ -0,0 +1,49 @@ +import { + BroadcastAmqClient, + BroadcastMessage, + BroadcastMessageContentMapper, + BroadcastOptions, +} from '../common/broadcast'; +import { TraceProcessorBroadcastMapper } from './tasks/trace-processor.mapper'; +import { DeltaProcessorBroadcastMapper } from './tasks/delta-processor.mapper'; + +export abstract class ProcessorBroadcastEmmiter { + public abstract sendMessage(data: TaskType): Promise; +} + +export class ProcessorBroadcast + implements ProcessorBroadcastEmmiter +{ + constructor(private client: BroadcastAmqClient, private channel: string) {} + + public sendMessage(data: TaskType): Promise { + return this.client.sendMessage(this.channel, data); + } + + public onMessage(handler: (message: BroadcastMessage) => void): void { + return this.client.onMessage(this.channel, handler); + } +} + +export const createProcessorBroadcastOptions = ( + actionProcessorMapper?: BroadcastMessageContentMapper, + deltaProcessorMapper?: BroadcastMessageContentMapper +): BroadcastOptions => { + return { + prefetch: 1, + queues: [ + { + name: 'action_processor', + options: { durable: true }, + mapper: actionProcessorMapper || new TraceProcessorBroadcastMapper(), + noAck: false, + }, + { + name: 'delta_processor', + options: { durable: true }, + mapper: deltaProcessorMapper || new DeltaProcessorBroadcastMapper(), + noAck: false, + }, + ], + }; +}; diff --git a/src/processor/processor.config.ts b/src/processor/processor.config.ts new file mode 100644 index 0000000..edb6032 --- /dev/null +++ b/src/processor/processor.config.ts @@ -0,0 +1,6 @@ +export type ProcessorConfig = { + broadcast: { + url: string; + }; + threads: number; +}; diff --git a/src/processor/start-processor.ts b/src/processor/start-processor.ts new file mode 100644 index 0000000..971b97c --- /dev/null +++ b/src/processor/start-processor.ts @@ -0,0 +1,76 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-misused-promises */ +import { + BroadcastMessage, + BroadcastMessageContentMapper, + setupBroadcast, +} from '../common/broadcast'; +import { WorkerMessage } from '../common/workers/worker-message'; +import { WorkerPool } from '../common/workers/worker-pool'; +import { createProcessorBroadcastOptions } from './processor.broadcast'; +import { ProcessorConfig } from './processor.config'; +import { TraceProcessorTaskInput } from './tasks/trace-processor.task-input'; + +/** + * + * @param message + * @param processors + * @param workerPool + */ +export const handleProcessorMessage = ( + message: BroadcastMessage, + processors: Map, + workerPool: WorkerPool +) => { + const { content } = message; + const processorPath = processors.get(content.allocation); + const worker = workerPool.getWorker(processorPath); + + if (worker && processorPath) { + worker.onMessage(async (workerMessage: WorkerMessage) => { + if (workerMessage.isTaskResolved()) { + message.ack(); + } else { + message.reject(); + } + await workerPool.releaseWorker(workerMessage.pid); + }); + worker.onError(error => { + message.postpone(); + }); + worker.run(content); + } else if (!worker && processorPath) { + // We have a defined processor for this particular action, + // but there are no free resources to run it, + // so we need to postpone the execution + message.postpone(); + } else { + // We do not have a defined processor for this particular action, + // so we have to discard it so that it does not stay in the queue + message.reject(); + } +}; + +/** + * + * @param processors + * @param broadcastMessageMapper + * @param config + */ +export const startProcessor = async ( + config: ProcessorConfig, + processors: Map, + broadcastMessageMapper?: BroadcastMessageContentMapper +) => { + const { + broadcast: { url }, + } = config; + const broadcastOptions = createProcessorBroadcastOptions(broadcastMessageMapper); + const channel = broadcastOptions.queues[0].name; + const broadcast = await setupBroadcast(url, broadcastOptions); + const workerPool = new WorkerPool({ threadsCount: config.threads }); + + broadcast.onMessage(channel, (message: BroadcastMessage) => + handleProcessorMessage(message, processors, workerPool) + ); +}; diff --git a/src/processor/tasks/delta-processor.mapper.ts b/src/processor/tasks/delta-processor.mapper.ts new file mode 100644 index 0000000..1fc8511 --- /dev/null +++ b/src/processor/tasks/delta-processor.mapper.ts @@ -0,0 +1,62 @@ +import { Serialize } from 'eosjs'; +import { BroadcastMessageContentMapper } from '../../common/broadcast'; +import { DeltaProcessorTaskInput } from './delta-processor.task-input'; + +export class DeltaProcessorBroadcastMapper + implements BroadcastMessageContentMapper +{ + public toContent(buffer: Buffer): DeltaProcessorTaskInput { + const sb = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + array: new Uint8Array(buffer), + }); + + const blockNumber: bigint = Buffer.from(sb.getUint8Array(8)).readBigInt64BE(); + const present = sb.get(); + const timestamp = Buffer.from(sb.getUint8Array(4)).readUInt32BE(0); + const blockTimestamp = new Date(timestamp * 1000); + sb.get(); // version + const code = sb.getName(); + const scope = sb.getName(); + const table = sb.getName(); + const allocation = sb.getName(); + const primaryKey = Buffer.from(sb.getUint8Array(8)).readBigInt64BE(); + const payer = sb.getName(); + const data = sb.getBytes(); + + return DeltaProcessorTaskInput.create( + blockNumber, + blockTimestamp, + present, + code, + scope, + table, + primaryKey, + payer, + data, + allocation + ); + } + + public toSource(input: DeltaProcessorTaskInput): Buffer { + const { blockNumber, blockTimestamp, present, data, allocation } = input; + + const deltaSb = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + }); + + deltaSb.pushName(allocation); + deltaSb.pushBytes(data); + + const blockBuffer = Buffer.alloc(8); + blockBuffer.writeBigInt64BE(blockNumber); + const timestampBuffer = Buffer.alloc(4); + timestampBuffer.writeUInt32BE(blockTimestamp.getTime() / 1000); + const presentBuffer = Buffer.from([present]); + const dataBuffer = Buffer.from(deltaSb.array); + + return Buffer.concat([blockBuffer, presentBuffer, timestampBuffer, dataBuffer]); + } +} diff --git a/src/processor/tasks/delta-processor.task-input.ts b/src/processor/tasks/delta-processor.task-input.ts new file mode 100644 index 0000000..462b626 --- /dev/null +++ b/src/processor/tasks/delta-processor.task-input.ts @@ -0,0 +1,68 @@ +import { DeltaRow } from '../../common/blockchain/block-content'; + +export class DeltaProcessorTaskInput { + public static fromBlockchainData( + name: string, + code: string, + scope: string, + table: string, + blockNumber: bigint, + blockTimestamp: Date, + row: DeltaRow, + ) { + const { present, data } = row; + const allocation = `${name}:${code}:${scope}:${table}`; + + return new DeltaProcessorTaskInput( + blockNumber, + blockTimestamp, + present, + code, + scope, + table, + null, + null, + data, + allocation + ); + } + + public static create( + blockNumber: bigint, + blockTimestamp: Date, + present: number, + code: string, + scope: string, + table: string, + primaryKey: bigint, + payer: string, + data: Uint8Array, + allocation: string + ) { + return new DeltaProcessorTaskInput( + blockNumber, + blockTimestamp, + present, + code, + scope, + table, + primaryKey, + payer, + data, + allocation + ); + } + + private constructor( + public readonly blockNumber: bigint, + public readonly blockTimestamp: Date, + public readonly present: number, + public readonly code: string, + public readonly scope: string, + public readonly table: string, + public readonly primaryKey: bigint, + public readonly payer: string, + public readonly data: Uint8Array, + public readonly allocation: string + ) {} +} diff --git a/src/processor/tasks/trace-processor.mapper.ts b/src/processor/tasks/trace-processor.mapper.ts new file mode 100644 index 0000000..271da60 --- /dev/null +++ b/src/processor/tasks/trace-processor.mapper.ts @@ -0,0 +1,83 @@ +import { Serialize } from 'eosjs'; +import { arrayToHex, hexToUint8Array } from 'eosjs/dist/eosjs-serialize'; +import { BroadcastMessageContentMapper } from '../../common/broadcast'; +import { TraceProcessorTaskInput } from './trace-processor.task-input'; + +export class TraceProcessorBroadcastMapper + implements BroadcastMessageContentMapper +{ + public toContent(content: Buffer): TraceProcessorTaskInput { + const sb = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + array: new Uint8Array(content), + }); + + const blockNumber: bigint = Buffer.from(sb.getUint8Array(8)).readBigInt64BE(); + const timestamp = Buffer.from(sb.getUint8Array(4)).readUInt32BE(0); + const blockTimestamp = new Date(timestamp * 1000); + const transactionId = arrayToHex(sb.getUint8Array(32)); + const recvSequence = Buffer.from(sb.getUint8Array(8)).readBigInt64BE(); + const globalSequence = Buffer.from(sb.getUint8Array(8)).readBigInt64BE(); + const account = sb.getName(); + const name = sb.getName(); + const allocation = sb.getName(); + const data = sb.getBytes(); + + return TraceProcessorTaskInput.create( + blockNumber, + blockTimestamp, + transactionId, + account, + name, + recvSequence, + globalSequence, + data, + allocation + ); + } + + public toSource(input: TraceProcessorTaskInput): Buffer { + const { + transactionId, + account, + name, + recvSequence, + globalSequence, + data, + blockNumber, + blockTimestamp, + allocation, + } = input; + + const actionSb = new Serialize.SerialBuffer({ + textEncoder: new TextEncoder(), + textDecoder: new TextDecoder(), + }); + + actionSb.pushName(account); + actionSb.pushName(name); + actionSb.pushName(allocation); + actionSb.pushBytes(data); + + const blockBuffer = Buffer.alloc(8); + blockBuffer.writeBigInt64BE(blockNumber); + const timestampBuffer = Buffer.alloc(4); + timestampBuffer.writeUInt32BE(blockTimestamp.getTime() / 1000); + const transactionIdBuffer = Buffer.from(hexToUint8Array(transactionId)); + const recvBuffer = Buffer.alloc(8); + recvBuffer.writeBigInt64BE(recvSequence); + const globalBuffer = Buffer.alloc(8); + globalBuffer.writeBigInt64BE(globalSequence); + const actionBuffer = Buffer.from(actionSb.array); + + return Buffer.concat([ + blockBuffer, + timestampBuffer, + transactionIdBuffer, + recvBuffer, + globalBuffer, + actionBuffer, + ]); + } +} diff --git a/src/processor/tasks/trace-processor.task-input.ts b/src/processor/tasks/trace-processor.task-input.ts new file mode 100644 index 0000000..d4ed525 --- /dev/null +++ b/src/processor/tasks/trace-processor.task-input.ts @@ -0,0 +1,65 @@ +import { ActionTrace } from '../../common/blockchain/block-content'; + +export class TraceProcessorTaskInput { + public static fromBlockchainData( + transactionId: string, + actionTrace: ActionTrace, + blockNumber: bigint, + blockTimestamp: Date + ) { + const { + type, + act: { account, name, data }, + receipt: { recvSequence, globalSequence }, + } = actionTrace; + const allocation = `${type}:${account}:${name}`; + + return new TraceProcessorTaskInput( + blockNumber, + blockTimestamp, + transactionId, + account, + name, + recvSequence, + globalSequence, + data, + allocation + ); + } + + public static create( + blockNumber: bigint, + blockTimestamp: Date, + transactionId: string, + account: string, + name: string, + recvSequence: bigint, + globalSequence: bigint, + data: Uint8Array, + allocation: string + ) { + return new TraceProcessorTaskInput( + blockNumber, + blockTimestamp, + transactionId, + account, + name, + recvSequence, + globalSequence, + data, + allocation + ); + } + + private constructor( + public readonly blockNumber: bigint, + public readonly blockTimestamp: Date, + public readonly transactionId: string, + public readonly account: string, + public readonly name: string, + public readonly recvSequence: bigint, + public readonly globalSequence: bigint, + public readonly data: Uint8Array, + public readonly allocation: string + ) {} +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..305e8ed --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "exclude": [ + "src/**/__tests__/*", + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..646f8e2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "commonjs", + "esModuleInterop": true, + "removeComments": true, + "preserveConstEnums": true, + "declaration": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "outDir": "build", + }, + "include": ["src/**/*"], + "exclude": [ + "node_modules", + "build" + ] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..a40c33a --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3598 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@acuminous/bitsyntax@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz#e0b31b9ee7ad1e4dd840c34864327c33d9f1f653" + integrity sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ== + dependencies: + buffer-more-ints "~1.0.0" + debug "^4.3.4" + safe-buffer "~5.1.2" + +"@alien-worlds/api-core@^0.0.27": + version "0.0.27" + resolved "https://npm.pkg.github.com/download/@alien-worlds/api-core/0.0.27/e8a86c8f093074d5058cbc8bbdf51eaeb3c04206#e8a86c8f093074d5058cbc8bbdf51eaeb3c04206" + integrity sha512-fKfrPP2upn/QJ/bcbB2eUzD4qCS6dZtFXmjeD4Kf/Iyc7lPhew/NUSq4GzlhpL/U109y6zHCr7sTa8wf8A5GLA== + dependencies: + eosjs "^22.1.0" + inversify "^6.0.1" + mongodb "4.9.1" + reflect-metadata "^0.1.13" + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.0.tgz#9b61938c5f688212c7b9ae363a819df7d29d4093" + integrity sha512-Gt9jszFJYq7qzXVK4slhc6NzJXnOVmRECWcVjF/T23rNXD9NtWQ0W3qxdg+p9wWIB+VQw3GYV/U2Ha9bRTfs4w== + +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.6.tgz#7122ae4f5c5a37c0946c066149abd8e75f81540f" + integrity sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.19.6" + "@babel/helper-compilation-targets" "^7.19.3" + "@babel/helper-module-transforms" "^7.19.6" + "@babel/helpers" "^7.19.4" + "@babel/parser" "^7.19.6" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@^7.19.6", "@babel/generator@^7.20.0", "@babel/generator@^7.7.2": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.0.tgz#0bfc5379e0efb05ca6092091261fcdf7ec36249d" + integrity sha512-GUPcXxWibClgmYJuIwC2Bc2Lg+8b9VjaJ+HlNdACEVt+Wlr1eoU1OPZjZRm7Hzl0gaTsUZNQfeihvZJhG7oc3w== + dependencies: + "@babel/types" "^7.20.0" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.19.3": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a" + integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ== + dependencies: + "@babel/compat-data" "^7.20.0" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.19.6": + version "7.19.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz#6c52cc3ac63b70952d33ee987cbee1c9368b533f" + integrity sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.19.4" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.19.6" + "@babel/types" "^7.19.4" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" + integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== + +"@babel/helper-simple-access@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz#be553f4951ac6352df2567f7daa19a0ee15668e7" + integrity sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg== + dependencies: + "@babel/types" "^7.19.4" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.19.4": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.0.tgz#27c8ffa8cc32a2ed3762fba48886e7654dbcf77f" + integrity sha512-aGMjYraN0zosCEthoGLdqot1oRsmxVTQRHadsUPz5QM44Zej2PYRz7XiDE7GqnkZnNtLbOuxqoZw42vkU7+XEQ== + dependencies: + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.20.0" + "@babel/types" "^7.20.0" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.10", "@babel/parser@^7.19.6", "@babel/parser@^7.20.0": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.0.tgz#b26133c888da4d79b0d3edcf42677bcadc783046" + integrity sha512-G9VgAhEaICnz8iiJeGJQyVl6J2nTjbW0xeisva0PK6XcKsga7BIaqm4ZF8Rg1Wbaqmy6znspNqhPaPkyukujzg== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" + integrity sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/template@^7.18.10", "@babel/template@^7.3.3": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@^7.19.6", "@babel/traverse@^7.20.0", "@babel/traverse@^7.7.2": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.0.tgz#538c4c6ce6255f5666eba02252a7b59fc2d5ed98" + integrity sha512-5+cAXQNARgjRUK0JWu2UBwja4JLSO/rBMPJzpsKb+oBF5xlUuCfljQepS4XypBQoiigL0VQjTZy6WiONtUdScQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.0" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.0" + "@babel/types" "^7.20.0" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.19.4", "@babel/types@^7.20.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.0.tgz#52c94cf8a7e24e89d2a194c25c35b17a64871479" + integrity sha512-Jlgt3H0TajCW164wkTOTzHkZb075tMQMULzrLUoUeKmO7eFL96GgDxf7/Axhc5CAuKE3KFyVW1p6ysKsi2oXAg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@eslint/eslintrc@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.3.tgz#2b044ab39fdfa75b4688184f9e573ce3c5b0ff95" + integrity sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.4.0" + globals "^13.15.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" + +"@humanwhocodes/config-array@^0.11.6": + version "0.11.6" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.6.tgz#6a51d603a3aaf8d4cf45b42b3f2ac9318a4adc4b" + integrity sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + 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" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + +"@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== + dependencies: + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== + dependencies: + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" + "@types/node" "*" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" + +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + 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-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^8.1.0" + +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.9" + source-map "^0.6.0" + +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== + dependencies: + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== + dependencies: + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" + +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.2.tgz#235bf339d17185bdec25e024ca19cce257cc7309" + integrity sha512-FcFaxOr2V5KZCviw1TnutEMVUVsGt4D2hP1TAfXZAMKuHYW3xQhe3jTxNPWutgCJ3/X1c5yX8ZoGVEItxKbwBg== + dependencies: + "@babel/types" "^7.3.0" + +"@types/graceful-fs@^4.1.2": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^27.0.3": + version "27.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" + integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + +"@types/json-schema@^7.0.9": + 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== + +"@types/node@*", "@types/node@^18.7.14": + version "18.11.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.7.tgz#8ccef136f240770c1379d50100796a6952f01f94" + integrity sha512-LhFTglglr63mNXUSRYD8A+ZAIu5sFqNJ4Y2fPuY7UlrySJH87rRRlhtVmMHplmfk5WkoJGmDjE9oiTfyX94CpQ== + +"@types/prettier@^2.1.5": + version "2.7.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.1.tgz#dfd20e2dc35f027cdd6c1908e80a5ddc7499670e" + integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== + +"@types/semver@^7.3.12": + version "7.3.13" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" + integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/webidl-conversions@*": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7" + integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog== + +"@types/whatwg-url@^8.2.1": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63" + integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.37.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz#f8eeb1c6bb2549f795f3ba71aec3b38d1ab6b1e1" + integrity sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA== + dependencies: + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/type-utils" "5.41.0" + "@typescript-eslint/utils" "5.41.0" + debug "^4.3.4" + ignore "^5.2.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.37.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.41.0.tgz#0414a6405007e463dc527b459af1f19430382d67" + integrity sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA== + dependencies: + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/typescript-estree" "5.41.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz#28e3a41d626288d0628be14cf9de8d49fc30fadf" + integrity sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ== + dependencies: + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/visitor-keys" "5.41.0" + +"@typescript-eslint/type-utils@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz#2371601171e9f26a4e6da918a7913f7266890cdf" + integrity sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA== + dependencies: + "@typescript-eslint/typescript-estree" "5.41.0" + "@typescript-eslint/utils" "5.41.0" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.41.0.tgz#6800abebc4e6abaf24cdf220fb4ce28f4ab09a85" + integrity sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA== + +"@typescript-eslint/typescript-estree@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz#bf5c6b3138adbdc73ba4871d060ae12c59366c61" + integrity sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg== + dependencies: + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/visitor-keys" "5.41.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.41.0.tgz#f41ae5883994a249d00b2ce69f4188f3a23fa0f9" + integrity sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.41.0" + "@typescript-eslint/types" "5.41.0" + "@typescript-eslint/typescript-estree" "5.41.0" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + semver "^7.3.7" + +"@typescript-eslint/visitor-keys@5.41.0": + version "5.41.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz#d3510712bc07d5540160ed3c0f8f213b73e3bcd9" + integrity sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw== + dependencies: + "@typescript-eslint/types" "5.41.0" + eslint-visitor-keys "^3.3.0" + +abab@^2.0.3, abab@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^7.1.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.2.4, acorn@^8.4.1, acorn@^8.8.0: + version "8.8.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + 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" + +amqplib@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.10.3.tgz#e186a2f74521eb55ec54db6d25ae82c29c1f911a" + integrity sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw== + dependencies: + "@acuminous/bitsyntax" "^0.1.2" + buffer-more-ints "~1.0.0" + readable-stream "1.x >=1.1.9" + url-parse "~1.5.10" + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + 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" + +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + 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" + +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== + dependencies: + babel-plugin-jest-hoist "^27.5.1" + babel-preset-current-node-syntax "^1.0.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bn.js@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== + +bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserslist@^4.21.3: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== + dependencies: + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" + +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +bson@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/bson/-/bson-4.7.0.tgz#7874a60091ffc7a45c5dd2973b5cad7cded9718a" + integrity sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA== + dependencies: + buffer "^5.6.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-more-ints@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz#ef4f8e2dddbad429ed3828a9c55d44f05c611422" + integrity sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg== + +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001400: + version "1.0.30001426" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001426.tgz#58da20446ccd0cb1dfebd11d2350c907ee7c2eaa" + integrity sha512-n7cosrHLl8AWt0wwZw/PJZgUg3lV0gk9LMI7ikGJwhyhgsd2Nb65vKvmSexCqq/J7rbH3mFG6yZZiPR5dLPW5A== + +chalk@^2.0.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +ci-info@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.5.0.tgz#bfac2a29263de4c829d806b1ab478e35091e171f" + integrity sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +data-uri-to-buffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" + integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decimal.js@^10.2.1: + version "10.4.2" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.2.tgz#0341651d1d997d86065a2ce3a441fbd0d8e8b98e" + integrity sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA== + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-is@^0.1.3, deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + +electron-to-chromium@^1.4.251: + version "1.4.284" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" + integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== + +elliptic@6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +eosjs@^22.1.0: + version "22.1.0" + resolved "https://registry.yarnpkg.com/eosjs/-/eosjs-22.1.0.tgz#7ac40e2f1f959fab70539c30ac8ae46c9038aa3c" + integrity sha512-Ka8KO7akC3RxNdSg/3dkGWuUWUQESTzSUzQljBdVP16UG548vmQoBqSGnZdnjlZyfcab8VOu2iEt+JjyfYc5+A== + dependencies: + bn.js "5.2.0" + elliptic "6.5.4" + hash.js "1.1.7" + pako "2.0.3" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-prettier@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@^8.23.1: + version "8.26.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.26.0.tgz#2bcc8836e6c424c4ac26a5674a70d44d84f2181d" + integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg== + dependencies: + "@eslint/eslintrc" "^1.3.3" + "@humanwhocodes/config-array" "^0.11.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.4.0" + esquery "^1.4.0" + 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.15.0" + grapheme-splitter "^1.0.4" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-sdsl "^4.1.4" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + +espree@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" + integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== + dependencies: + acorn "^8.8.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + 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" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== + dependencies: + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-glob@^3.2.9: + version "3.2.12" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" + integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + 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" + +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.7" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.15.0: + version "13.17.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" + integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + 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" + +graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +grapheme-splitter@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" + integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inversify@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/inversify/-/inversify-6.0.1.tgz#b20d35425d5d8c5cd156120237aad0008d969f02" + integrity sha512-B3ex30927698TJENHR++8FfEaJGqoWOgI6ZY5Ht/nLUsFCwHn6akbwtnUAPCgUepAnTpe2qHxhDNjoKLyz6rgQ== + +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" + integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + 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" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== + dependencies: + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== + dependencies: + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + prompts "^2.0.1" + yargs "^16.2.0" + +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== + dependencies: + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== + dependencies: + detect-newline "^3.0.0" + +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" + +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== + dependencies: + "@jest/types" "^27.5.1" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" + +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== + dependencies: + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== + +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== + dependencies: + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" + +jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-pnp-resolver "^1.2.2" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + source-map-support "^0.5.6" + throat "^6.0.1" + +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.9" + +jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + natural-compare "^1.4.0" + pretty-format "^27.5.1" + semver "^7.3.2" + +jest-util@^27.0.0, jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== + dependencies: + "@jest/types" "^27.5.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.5.1" + leven "^3.1.0" + pretty-format "^27.5.1" + +jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== + dependencies: + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^27.5.1" + string-length "^4.0.1" + +jest-worker@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== + dependencies: + "@jest/core" "^27.5.1" + import-local "^3.0.2" + jest-cli "^27.5.1" + +js-sdsl@^4.1.4: + version "4.1.5" + resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.1.5.tgz#1ff1645e6b4d1b028cd3f862db88c9d887f26e2a" + integrity sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q== + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.5.0" + ws "^7.4.6" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@2.x, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.memoize@4.x: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.7.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-error@1.x, make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +mongodb-connection-string-url@^2.5.3: + version "2.5.4" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.4.tgz#1ee2496f4c4eae64f63c4b2d512aebc89996160a" + integrity sha512-SeAxuWs0ez3iI3vvmLk/j2y+zHwigTDKQhtdxTgt5ZCOQQS5+HW4g45/Xw5vzzbn7oQXCNQ24Z40AkJsizEy7w== + dependencies: + "@types/whatwg-url" "^8.2.1" + whatwg-url "^11.0.0" + +mongodb@4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.9.1.tgz#0c769448228bcf9a6aa7d16daa3625b48312479e" + integrity sha512-ZhgI/qBf84fD7sI4waZBoLBNJYPQN5IOC++SBCiPiyhzpNKOxN/fi0tBHvH2dEC42HXtNEbFB0zmNz4+oVtorQ== + dependencies: + bson "^4.7.0" + denque "^2.1.0" + mongodb-connection-string-url "^2.5.3" + socks "^2.7.0" + optionalDependencies: + saslprep "^1.0.3" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.2.10: + version "3.2.10" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8" + integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== + +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nwsapi@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.3.tgz#cdf475e31b678565251406de9e759196a0ea7a43" + integrity sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw== + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + 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" + +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + +pretty-format@^27.0.0, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +"readable-stream@1.x >=1.1.9": + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.20.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saslprep@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +semver@7.x, semver@^7.3.2, semver@^7.3.7: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + +semver@^6.0.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks@^2.7.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" + integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + +source-map-support@^0.5.6: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-encoding@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.7.0.tgz#f895e836e45990624086601798ea98e8f36ee643" + integrity sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +throat@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +tough-cookie@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + +ts-jest@^27.1.3: + version "27.1.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297" + integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash.memoize "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "20.x" + +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + 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" + +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.8.2: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== + +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +update-browserslist-db@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +url-parse@^1.5.3, url-parse@~1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +v8-to-istanbul@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +web-streams-polyfill@^3.0.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3, word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yargs-parser@20.x, yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==