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==