diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f47b5ce --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +node_modules +public +build +dist \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..880ce38 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "overrides": [], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": ["@typescript-eslint", "unused-imports", "simple-import-sort"], + "rules": { + "unused-imports/no-unused-imports": "error", + "prefer-const": "error", + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "no-var": "error" + } +} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..d0a7784 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f47b5ce --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +node_modules +public +build +dist \ No newline at end of file diff --git a/.prettierrc.yml b/.prettierrc.yml new file mode 100644 index 0000000..a2cf2c2 --- /dev/null +++ b/.prettierrc.yml @@ -0,0 +1,9 @@ +printWidth: 100 +tabWidth: 2 +singleQuote: true +semi: false +jsxSingleQuote: true +quoteProps: as-needed +trailingComma: none +bracketSpacing: true +bracketSameLine: false diff --git a/package.json b/package.json index 212907b..7eb4f06 100755 --- a/package.json +++ b/package.json @@ -8,14 +8,24 @@ "dev": "ts-node-dev --respawn --inspect=9229 --transpile-only ./src/app.ts", "dev6001": "ts-node-dev --inspect=6001 --transpile-only ./src/app.ts", "dev6002": "ts-node-dev --inspect=6002 --transpile-only ./src/app.ts", - "heroku-postbuild": "npm run build", "start": "nodemon", "inspect": "nodemon --inspect src/app.ts", "test": "mocha --inspect=9229 -r ts-node/register tests/**/*.test.ts --require tests/root.ts --serial", - "lint": "npm run lint:js ", - "lint:eslint": "eslint --ignore-path .gitignore --ext .ts", - "lint:js": "npm run lint:eslint src/", - "lint:fix": "npm run lint:js -- --fix" + "lint": "eslint '**/*.{js,ts,jsx,tsx}'", + "lint:fix": "eslint --fix '**/*.{js,ts,jsx,tsx}'", + "format": "prettier --write '**/*.{js,ts,jsx,tsx}'", + "prepare": "husky" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ] }, "repository": { "type": "git", @@ -112,15 +122,21 @@ "@types/mongoose": "^5.3.17", "@types/mysql": "^2.15.21", "@types/node": "^10.17.60", - "@typescript-eslint/eslint-plugin": "^1.7.0", - "@typescript-eslint/parser": "^1.7.0", + "@typescript-eslint/eslint-plugin": "^7.16.1", + "@typescript-eslint/parser": "^7.16.1", "aws-sdk": "2.770.0", + "eslint": "^8.7.0", "eslint-config-prettier": "^4.2.0", "eslint-plugin-prettier": "^3.0.1", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-unused-imports": "^4.0.0", + "husky": "^9.0.11", "jest": "^24.1.0", + "lint-staged": "^15.2.7", "mocha": "^9.1.3", "nodemon": "^2.0.1", - "prettier": "^1.17.0", + "prettier": "^3.3.3", + "pretty-quick": "^4.0.0", "ts-jest": "^24.0.0", "ts-node": "^10.4.0", "ts-node-dev": "1.0.0-pre.44", diff --git a/src/api/index.ts b/src/api/index.ts index bdb68ff..f0d1881 100755 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,12 +1,12 @@ -import { Router } from 'express'; - -import {storageRoutes} from './routes/storageRoutes'; -import {ExpressUtil} from "../utilz/expressUtil"; - -// guaranteed to get dependencies -export default () => { - const app = Router(); - app.use(ExpressUtil.handle); - storageRoutes(app); - return app; -}; +import { Router } from 'express' + +import { ExpressUtil } from '../utilz/expressUtil' +import { storageRoutes } from './routes/storageRoutes' + +// guaranteed to get dependencies +export default () => { + const app = Router() + app.use(ExpressUtil.handle) + storageRoutes(app) + return app +} diff --git a/src/api/middlewares/onlyLocalhost.ts b/src/api/middlewares/onlyLocalhost.ts index 3c4f03a..bd008bd 100644 --- a/src/api/middlewares/onlyLocalhost.ts +++ b/src/api/middlewares/onlyLocalhost.ts @@ -1,9 +1,10 @@ import { Container } from 'typedi' + import config from '../../config' -var dns = require('dns') -var os = require('os') -var ifaces = os.networkInterfaces() +const dns = require('dns') +const os = require('os') +const ifaces = os.networkInterfaces() /** * @param {*} req Express req Object @@ -14,8 +15,8 @@ const onlyLocalhost = async (req, res, next) => { const Logger = Container.get('logger') try { // Check if ip is localhost and only continue - var ip = req.connection.remoteAddress - var host = req.get('host') + const ip = req.connection.remoteAddress + const host = req.get('host') if (config.environment === 'production') { // Return with unauthorized error diff --git a/src/api/routes/storageRoutes.ts b/src/api/routes/storageRoutes.ts index 5d31dae..b24142a 100755 --- a/src/api/routes/storageRoutes.ts +++ b/src/api/routes/storageRoutes.ts @@ -1,149 +1,165 @@ -import {Router, Request, Response, NextFunction} from 'express'; -import {Container} from 'typedi'; -import log from '../../loaders/logger'; -import DbHelper from '../../helpers/dbHelper'; -import bodyParser from "body-parser"; -import {DateTime} from "ts-luxon"; -import {ExpressUtil} from "../../utilz/expressUtil"; -import DateUtil from "../../utilz/dateUtil"; -import StrUtil from "../../utilz/strUtil"; -import {ValidatorContractState} from "../../services/messaging-common/validatorContractState"; -import onlyLocalhost from "../middlewares/onlyLocalhost"; -import StorageNode from "../../services/messaging/storageNode"; -import {Coll} from "../../utilz/coll"; -import {MessageBlockUtil} from "../../services/messaging-common/messageBlock"; -import {StorageContractState} from "../../services/messaging-common/storageContractState"; -import {EnvLoader} from "../../utilz/envLoader"; - -const PAGE_SIZE = Number.parseInt(EnvLoader.getPropertyOrDefault("PAGE_SIZE", "30")); - -const route = Router(); -const dbh = new DbHelper(); - +import bodyParser from 'body-parser' +import { NextFunction, Request, Response, Router } from 'express' +import { DateTime } from 'ts-luxon' +import { Container } from 'typedi' + +import DbHelper from '../../helpers/dbHelper' +import log from '../../loaders/logger' +import StorageNode from '../../services/messaging/storageNode' +import { MessageBlockUtil } from '../../services/messaging-common/messageBlock' +import { StorageContractState } from '../../services/messaging-common/storageContractState' +import { ValidatorContractState } from '../../services/messaging-common/validatorContractState' +import { Coll } from '../../utilz/coll' +import DateUtil from '../../utilz/dateUtil' +import { EnvLoader } from '../../utilz/envLoader' +import StrUtil from '../../utilz/strUtil' +import onlyLocalhost from '../middlewares/onlyLocalhost' + +const PAGE_SIZE = Number.parseInt(EnvLoader.getPropertyOrDefault('PAGE_SIZE', '30')) + +const route = Router() +const dbh = new DbHelper() function logRequest(req: Request) { - log.debug('Calling %o %o with body: %o', req.method, req.url, req.body); + log.debug('Calling %o %o with body: %o', req.method, req.url, req.body) } // todo ValidatorContractState export function storageRoutes(app: Router) { - app.use(bodyParser.json()); + app.use(bodyParser.json()) app.post( '/v1/reshard', onlyLocalhost, async (req: Request, res: Response, next: NextFunction) => { - logRequest(req); - const storageNode = Container.get(StorageNode); + logRequest(req) + const storageNode = Container.get(StorageNode) // ex: { "arr": [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14] } - let arr:number[] = req.body.arr; - let set = Coll.arrayToSet(arr); + const arr: number[] = req.body.arr + const set = Coll.arrayToSet(arr) await storageNode.handleReshard(set) - return res.status(200); - }); - - app.use('/v1/kv', route); + return res.status(200) + } + ) + app.use('/v1/kv', route) // todo move to StorageNode route.get( '/ns/:nsName/nsidx/:nsIndex/date/:dt/key/:key', async (req: Request, res: Response, next: NextFunction) => { - logRequest(req); - const nsName = req.params.nsName; - const nsIndex = req.params.nsIndex; - const dt = req.params.dt; - const key = req.params.key; - - const valContractState = Container.get(ValidatorContractState); - const storageContractState = Container.get(StorageContractState); - const nodeId = valContractState.nodeId; // todo read this from db - log.debug(`nsName=${nsName} nsIndex=${nsIndex} dt=${dt} key=${key} nodeId=${nodeId}`); - let shardId = MessageBlockUtil.calculateAffectedShard(nsIndex, storageContractState.shardCount); - - const date = DateTime.fromISO(dt, {zone: 'utc'}); + logRequest(req) + const nsName = req.params.nsName + const nsIndex = req.params.nsIndex + const dt = req.params.dt + const key = req.params.key + + const valContractState = Container.get(ValidatorContractState) + const storageContractState = Container.get(StorageContractState) + const nodeId = valContractState.nodeId // todo read this from db + log.debug(`nsName=${nsName} nsIndex=${nsIndex} dt=${dt} key=${key} nodeId=${nodeId}`) + const shardId = MessageBlockUtil.calculateAffectedShard( + nsIndex, + storageContractState.shardCount + ) + + const date = DateTime.fromISO(dt, { zone: 'utc' }) if (!date.isValid) { - return res.status(400).json('Invalid date ' + dt); + return res.status(400).json('Invalid date ' + dt) } log.debug(`parsed date ${dt} -> ${date}`) - const storageTable = await DbHelper.findStorageTableByDate(nsName, shardId, date); + const storageTable = await DbHelper.findStorageTableByDate(nsName, shardId, date) log.debug(`found table ${storageTable}`) if (StrUtil.isEmpty(storageTable)) { - log.error('storage table not found'); - return res.status(401).json('storage table not found'); + log.error('storage table not found') + return res.status(401).json('storage table not found') } - const storageItems = await DbHelper.findStorageItem(nsName, nsIndex, storageTable, key); + const storageItems = await DbHelper.findStorageItem(nsName, nsIndex, storageTable, key) log.debug(`found value: ${storageItems}`) try { return res.status(200).json({ items: storageItems - }); + }) } catch (e) { - return next(e); + return next(e) } } - ); - - + ) // todo move to StorageNode // todo not tested with new sharing (we don't use it anymore) route.post( - '/ns/:nsName/nsidx/:nsIndex/ts/:ts/key/:key', /* */ + '/ns/:nsName/nsidx/:nsIndex/ts/:ts/key/:key' /* */, async (req: Request, res: Response, next: NextFunction) => { - logRequest(req); - const nsName = req.params.nsName; // ex: feeds - const nsIndex = req.params.nsIndex; // ex: 1000000 - const ts: string = req.params.ts; //ex: 1661214142.123456 - const key = req.params.key; // ex: 5b62a7b2-d6eb-49ef-b080-20a7fa3091ad - const valContractState = Container.get(ValidatorContractState); - const storageContractState = Container.get(StorageContractState); - - const nodeId = valContractState.nodeId; - const body = JSON.stringify(req.body); - log.debug(`nsName=${nsName} nsIndex=${nsIndex} ts=${ts} key=${key} nodeId=${nodeId} body=${body}`); - let shardId = MessageBlockUtil.calculateAffectedShard(nsIndex, storageContractState.shardCount); - log.debug(`nodeId=${nodeId} shardId=${shardId}`); - const success = await DbHelper.checkThatShardIsOnThisNode(nsName, shardId, nodeId); + logRequest(req) + const nsName = req.params.nsName // ex: feeds + const nsIndex = req.params.nsIndex // ex: 1000000 + const ts: string = req.params.ts //ex: 1661214142.123456 + const key = req.params.key // ex: 5b62a7b2-d6eb-49ef-b080-20a7fa3091ad + const valContractState = Container.get(ValidatorContractState) + const storageContractState = Container.get(StorageContractState) + + const nodeId = valContractState.nodeId + const body = JSON.stringify(req.body) + log.debug( + `nsName=${nsName} nsIndex=${nsIndex} ts=${ts} key=${key} nodeId=${nodeId} body=${body}` + ) + const shardId = MessageBlockUtil.calculateAffectedShard( + nsIndex, + storageContractState.shardCount + ) + log.debug(`nodeId=${nodeId} shardId=${shardId}`) + const success = await DbHelper.checkThatShardIsOnThisNode(nsName, shardId, nodeId) if (!success) { - let errMsg = `${nsName}.${nsIndex} maps to shard ${shardId} which is missing on node ${nodeId}`; - console.log(errMsg); - return res.status(500) - .json({errorMessage: errMsg}) + const errMsg = `${nsName}.${nsIndex} maps to shard ${shardId} which is missing on node ${nodeId}` + console.log(errMsg) + return res.status(500).json({ errorMessage: errMsg }) } - const date = DateUtil.parseUnixFloatAsDateTime(ts); + const date = DateUtil.parseUnixFloatAsDateTime(ts) log.debug(`parsed date ${ts} -> ${date}`) - var storageTable = await DbHelper.findStorageTableByDate(nsName, shardId, date); + const storageTable = await DbHelper.findStorageTableByDate(nsName, shardId, date) log.debug(`found table ${storageTable}`) if (StrUtil.isEmpty(storageTable)) { - log.error('storage table not found'); - var monthStart = date.startOf('month').toISODate().toString(); - var monthEndExclusive = date.startOf('month').plus({months: 1}).toISODate().toString(); - log.debug('creating new storage table'); - const dateYYYYMM = DateUtil.formatYYYYMM(date); - const tableName = `storage_ns_${nsName}_d_${dateYYYYMM}`; - const recordCreated = await DbHelper.createNewNodestorageRecord(nsName, shardId, - monthStart, monthEndExclusive, tableName); + log.error('storage table not found') + const monthStart = date.startOf('month').toISODate().toString() + const monthEndExclusive = date.startOf('month').plus({ months: 1 }).toISODate().toString() + log.debug('creating new storage table') + const dateYYYYMM = DateUtil.formatYYYYMM(date) + const tableName = `storage_ns_${nsName}_d_${dateYYYYMM}` + const recordCreated = await DbHelper.createNewNodestorageRecord( + nsName, + shardId, + monthStart, + monthEndExclusive, + tableName + ) if (recordCreated) { log.debug('record created: ', recordCreated) // we've added a new record to node_storage_layout => we can safely try to create a table // otherwise, if many connections attempt to create a table from multiple threads // it leads to postgres deadlock sometimes - await DbHelper.createNewStorageTable(tableName); + await DbHelper.createNewStorageTable(tableName) log.debug('creating node storage layout mapping') } } - var storageTable = await DbHelper.findStorageTableByDate(nsName, shardId, date); - const storageValue = await DbHelper.putValueInTable(nsName, shardId, nsIndex, storageTable, ts, key, body); + const storageValue = await DbHelper.putValueInTable( + nsName, + shardId, + nsIndex, + storageTable, + ts, + key, + body + ) log.debug(`found value: ${storageValue}`) - log.debug('success is ' + success); + log.debug('success is ' + success) try { - return res.status(201).json(storageValue); + return res.status(201).json(storageValue) } catch (e) { - return next(e); + return next(e) } } - ); + ) /* Search for namespace:namespaceIndex data , ordered by timestamp asc, paginated by timestamp (!) Pagination is achieved by using firstTs parameter, passed from the previous invocation @@ -151,54 +167,63 @@ export function storageRoutes(app: Router) { * */ // todo move to StorageNode route.post( - '/ns/:nsName/nsidx/:nsIndex/month/:month/list/', /* */ + '/ns/:nsName/nsidx/:nsIndex/month/:month/list/' /* */, async (req: Request, res: Response, next: NextFunction) => { - logRequest(req); + logRequest(req) // we will search for data starting from this key exclusive // use lastTs from the previous request to get more data - const firstTs: string = req.query.firstTs; - const nsName = req.params.nsName; - const nsIndex = req.params.nsIndex; - const dt = req.params.month + '01'; - - const valContractState = Container.get(ValidatorContractState); - const storageContractState = Container.get(StorageContractState); - const nodeId = valContractState.nodeId; // todo read this from db - log.debug(`nsName=${nsName} nsIndex=${nsIndex} dt=${dt} nodeId=${nodeId} PAGE_SIZE=${PAGE_SIZE}`); - let shardId = MessageBlockUtil.calculateAffectedShard(nsIndex, storageContractState.shardCount); - const date = DateTime.fromISO(dt, {zone: 'utc'}); + const firstTs: string = req.query.firstTs + const nsName = req.params.nsName + const nsIndex = req.params.nsIndex + const dt = req.params.month + '01' + + const valContractState = Container.get(ValidatorContractState) + const storageContractState = Container.get(StorageContractState) + const nodeId = valContractState.nodeId // todo read this from db + log.debug( + `nsName=${nsName} nsIndex=${nsIndex} dt=${dt} nodeId=${nodeId} PAGE_SIZE=${PAGE_SIZE}` + ) + const shardId = MessageBlockUtil.calculateAffectedShard( + nsIndex, + storageContractState.shardCount + ) + const date = DateTime.fromISO(dt, { zone: 'utc' }) if (!date.isValid) { - return res.status(400).json('Invalid date ' + dt); + return res.status(400).json('Invalid date ' + dt) } log.debug(`parsed date ${dt} -> ${date}`) - const storageTable = await DbHelper.findStorageTableByDate(nsName, shardId, date); + const storageTable = await DbHelper.findStorageTableByDate(nsName, shardId, date) log.debug(`found table ${storageTable}`) if (StrUtil.isEmpty(storageTable)) { - log.error('storage table not found'); - return res.status(401).json('storage table not found'); + log.error('storage table not found') + return res.status(401).json('storage table not found') } - const storageValue = await DbHelper.listInbox(nsName, shardId, nsIndex, storageTable, firstTs, PAGE_SIZE); + const storageValue = await DbHelper.listInbox( + nsName, + shardId, + nsIndex, + storageTable, + firstTs, + PAGE_SIZE + ) log.debug(`found value: ${storageValue}`) try { - return res.status(200).json(storageValue); + return res.status(200).json(storageValue) } catch (e) { - return next(e); + return next(e) } } - ); + ) // prints all namespaces - route.post( - '/ns/all/', /* */ - async (req: Request, res: Response, next: NextFunction) => { - logRequest(req); - const allNsIndex = await DbHelper.listAllNsIndex(); - try { - return res.status(200).json(allNsIndex); - } catch (e) { - return next(e); - } + route.post('/ns/all/' /* */, async (req: Request, res: Response, next: NextFunction) => { + logRequest(req) + const allNsIndex = await DbHelper.listAllNsIndex() + try { + return res.status(200).json(allNsIndex) + } catch (e) { + return next(e) } - ); -}; + }) +} // todo remove logic from router diff --git a/src/app.ts b/src/app.ts index aae0627..0d9dc08 100755 --- a/src/app.ts +++ b/src/app.ts @@ -1,9 +1,3 @@ - - - - - - import { startServer } from './appInit' // Call server from here to ensure test cases run fine diff --git a/src/appInit.ts b/src/appInit.ts index 09a5a34..7319e01 100755 --- a/src/appInit.ts +++ b/src/appInit.ts @@ -1,39 +1,41 @@ -import {EnvLoader} from "./utilz/envLoader"; -EnvLoader.loadEnvOrFail(); +import { EnvLoader } from './utilz/envLoader' +EnvLoader.loadEnvOrFail() -import 'reflect-metadata'; // We need this in order to use @Decorators -import express from 'express'; -import chalk from 'chalk'; -import {Container} from "typedi"; -import StorageNode from "./services/messaging/storageNode"; -import {ValidatorContractState} from "./services/messaging-common/validatorContractState"; -import {MySqlUtil} from "./utilz/mySqlUtil"; +import 'reflect-metadata' // We need this in order to use @Decorators +import chalk from 'chalk' +import express from 'express' +import { Container } from 'typedi' + +import StorageNode from './services/messaging/storageNode' async function startServer(logLevel = null) { if (logLevel) { - const changeLogLevel = (await require('./config/index')).changeLogLevel; - changeLogLevel(logLevel); + const changeLogLevel = (await require('./config/index')).changeLogLevel + changeLogLevel(logLevel) } // Continue Loading normally - const config = (await require('./config/index')).default; - logLevel = logLevel || config.logs.level; + const config = (await require('./config/index')).default + logLevel = logLevel || config.logs.level // ONLY TIME CONSOLE IS USED - console.log(chalk.bold.inverse('RUNNING WITH LOG LEVEL '), chalk.bold.blue.inverse(` ${logLevel} `)); + console.log( + chalk.bold.inverse('RUNNING WITH LOG LEVEL '), + chalk.bold.blue.inverse(` ${logLevel} `) + ) // Load logger - const Logger = (await require('./loaders/logger')).default; + const Logger = (await require('./loaders/logger')).default - await require('./api/index'); - await require('./loaders/express'); - Container.set("logger", Logger); + await require('./api/index') + await require('./loaders/express') + Container.set('logger', Logger) - await Container.get(StorageNode).postConstruct(); + await Container.get(StorageNode).postConstruct() // load app - const app = express(); - const server = require("http").createServer(app); + const app = express() + const server = require('http').createServer(app) /** * A little hack here @@ -41,26 +43,26 @@ async function startServer(logLevel = null) { * Well, at least in node 10 without babel and at the time of writing * So we are using good old require. **/ - await require('./loaders').default({ expressApp: app, server: server }); + await require('./loaders').default({ expressApp: app, server: server }) - server.listen(config.port, err => { + server.listen(config.port, (err) => { if (err) { - Logger.error(err); - process.exit(1); - return; + Logger.error(err) + process.exit(1) + return } Logger.info(` ################################################ STARTED 🛡️ Server listening on port: ${config.port} 🛡️ ################################################ - `); - }); + `) + }) } // stopServer shuts down the server. Used in tests. async function stopServer() { - process.exit(0); + process.exit(0) } -export { startServer, stopServer }; +export { startServer, stopServer } diff --git a/src/config/index.ts b/src/config/index.ts index 8e249af..8ac0858 100755 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -1,24 +1,22 @@ // import {logLevel} from '../app' // Set the NODE_ENV to 'development' by default -process.env.NODE_ENV = process.env.NODE_ENV || 'development'; - +process.env.NODE_ENV = process.env.NODE_ENV || 'development' export const changeLogLevel = (level: string) => { if (level) { } -}; +} // console.log("-------------custom------", logLevel) export default { - environment: process.env.NODE_ENV, - port: parseInt((process.env.PORT || '3000'), 10), + port: parseInt(process.env.PORT || '3000', 10), runningOnMachine: process.env.RUNNING_ON_MACHINE, logs: { - level: process.env.LOG_LEVEL || 'silly', + level: process.env.LOG_LEVEL || 'silly' }, dbhost: process.env.DB_HOST, @@ -30,9 +28,15 @@ export default { /** * File system config */ - fsServerURL: process.env.NODE_ENV == 'development' ? process.env.FS_SERVER_DEV : process.env.FS_SERVER_PROD, + fsServerURL: + process.env.NODE_ENV == 'development' ? process.env.FS_SERVER_DEV : process.env.FS_SERVER_PROD, staticServePath: process.env.SERVE_STATIC_FILES, - staticCachePath: __dirname + '/../../' + process.env.SERVE_STATIC_FILES + '/' + process.env.SERVE_CACHE_FILES + '/', - staticAppPath: __dirname + '/../../', - -}; + staticCachePath: + __dirname + + '/../../' + + process.env.SERVE_STATIC_FILES + + '/' + + process.env.SERVE_CACHE_FILES + + '/', + staticAppPath: __dirname + '/../../' +} diff --git a/src/helpers/dbHelper.ts b/src/helpers/dbHelper.ts index 4621112..fece671 100755 --- a/src/helpers/dbHelper.ts +++ b/src/helpers/dbHelper.ts @@ -1,27 +1,26 @@ -import log from '../loaders/logger'; -import pgPromise from 'pg-promise'; - -import {DateTime} from "ts-luxon"; -import StrUtil from "../utilz/strUtil"; -import {EnvLoader} from "../utilz/envLoader"; -import {MySqlUtil} from "../utilz/mySqlUtil"; -import {PgUtil} from "../utilz/pgUtil"; -import {IClient} from "pg-promise/typescript/pg-subset"; - // mysql -import crypto from "crypto"; -import {WinstonUtil} from "../utilz/winstonUtil"; - -var mysql = require('mysql') -var mysqlPool = mysql.createPool({ - connectionLimit: 10, - host: EnvLoader.getPropertyOrFail('DB_HOST'), - user: EnvLoader.getPropertyOrFail('DB_USER'), - password: EnvLoader.getPropertyOrFail('DB_PASS'), - database: EnvLoader.getPropertyOrFail('DB_NAME'), - port: Number(EnvLoader.getPropertyOrFail('DB_NAME')) +import crypto from 'crypto' +import pgPromise from 'pg-promise' +import { IClient } from 'pg-promise/typescript/pg-subset' +import { DateTime } from 'ts-luxon' + +import log from '../loaders/logger' +import { EnvLoader } from '../utilz/envLoader' +import { MySqlUtil } from '../utilz/mySqlUtil' +import { PgUtil } from '../utilz/pgUtil' +import StrUtil from '../utilz/strUtil' +import { WinstonUtil } from '../utilz/winstonUtil' + +const mysql = require('mysql') +const mysqlPool = mysql.createPool({ + connectionLimit: 10, + host: EnvLoader.getPropertyOrFail('DB_HOST'), + user: EnvLoader.getPropertyOrFail('DB_USER'), + password: EnvLoader.getPropertyOrFail('DB_PASS'), + database: EnvLoader.getPropertyOrFail('DB_NAME'), + port: Number(EnvLoader.getPropertyOrFail('DB_NAME')) }) -MySqlUtil.init(mysqlPool); +MySqlUtil.init(mysqlPool) // postgres // todo fix variable substitution, see #putValueInTable() @@ -29,23 +28,24 @@ MySqlUtil.init(mysqlPool); // todo use PgUtil // todo use placeholders (?) -let logger = WinstonUtil.newLog('pg'); -let options = { - query: function (e) { - logger.debug('', e.query); - if (e.params) { - logger.debug('PARAMS: ', e.params); - } +const logger = WinstonUtil.newLog('pg') +const options = { + query: function (e) { + logger.debug('', e.query) + if (e.params) { + logger.debug('PARAMS: ', e.params) } -}; -const pg: pgPromise.IMain<{}, IClient> = pgPromise(options); -export const pgPool = pg(`postgres://${EnvLoader.getPropertyOrFail('PG_USER')}:${EnvLoader.getPropertyOrFail('PG_PASS')}@${EnvLoader.getPropertyOrFail('PG_HOST')}:5432/${EnvLoader.getPropertyOrFail('PG_NAME')}`); -PgUtil.init(pgPool); + } +} +const pg: pgPromise.IMain<{}, IClient> = pgPromise(options) +export const pgPool = pg( + `postgres://${EnvLoader.getPropertyOrFail('PG_USER')}:${EnvLoader.getPropertyOrFail('PG_PASS')}@${EnvLoader.getPropertyOrFail('PG_HOST')}:5432/${EnvLoader.getPropertyOrFail('PG_NAME')}` +) +PgUtil.init(pgPool) export default class DbHelper { - - public static async createStorageTablesIfNeeded() { - await PgUtil.update(` + public static async createStorageTablesIfNeeded() { + await PgUtil.update(` CREATE TABLE IF NOT EXISTS node_storage_layout ( namespace VARCHAR(20) NOT NULL, @@ -55,8 +55,8 @@ export default class DbHelper { table_name VARCHAR(64) NOT NULL, PRIMARY KEY (namespace, namespace_shard_id, ts_start, ts_end) ); - `); - await PgUtil.update(` + `) + await PgUtil.update(` -- allows itself to be called on every call -- recreates a view, no more than once per day, which contains @@ -114,58 +114,67 @@ BEGIN END $$ LANGUAGE plpgsql; `) - } - - // maps key -> 8bit space (0..255) - // uses first 4bit from an md5 hash - public static calculateShardForNamespaceIndex(namespace: string, key: string): number { - let buf = crypto.createHash('md5').update(key).digest(); - let i32 = buf.readUInt32LE(0); - const i8bits = i32 % 32; // todo it's 0x20 now, not 0xff - log.debug('calculateShardForNamespaceIndex(): ', buf, '->', i32, '->', i8bits); - return i8bits; - } - - public static async checkIfStorageTableExists(): Promise { - const date = new Date(); - const dbdate = date.getFullYear().toString() + date.getMonth().toString(); - var sql = ` + } + + // maps key -> 8bit space (0..255) + // uses first 4bit from an md5 hash + public static calculateShardForNamespaceIndex(namespace: string, key: string): number { + const buf = crypto.createHash('md5').update(key).digest() + const i32 = buf.readUInt32LE(0) + const i8bits = i32 % 32 // todo it's 0x20 now, not 0xff + log.debug('calculateShardForNamespaceIndex(): ', buf, '->', i32, '->', i8bits) + return i8bits + } + + public static async checkIfStorageTableExists(): Promise { + const date = new Date() + const dbdate = date.getFullYear().toString() + date.getMonth().toString() + const sql = ` SELECT EXISTS( SELECT FROM pg_tables WHERE schemaname='public' AND tablename='storage_ns_inbox_d_${dbdate}') ` - console.log(sql) - return pgPool.query(sql).then(data => { - console.log(data) - return Promise.resolve(true) - }).catch(err => { - console.log(err); - return Promise.resolve(false); - }); - } - - - public static async createNewNodestorageRecord(namespace: string, namespaceShardId: number, ts_start: any, ts_end: any, table_name: string): Promise { - const sql = ` + console.log(sql) + return pgPool + .query(sql) + .then((data) => { + console.log(data) + return Promise.resolve(true) + }) + .catch((err) => { + console.log(err) + return Promise.resolve(false) + }) + } + + public static async createNewNodestorageRecord( + namespace: string, + namespaceShardId: number, + ts_start: any, + ts_end: any, + table_name: string + ): Promise { + const sql = ` insert into node_storage_layout (namespace, namespace_shard_id, ts_start, ts_end, table_name) values ($1, $2, $3, $4, $5) on conflict do nothing; ` - console.log(sql); - return pgPool.result(sql, [namespace, namespaceShardId, ts_start, ts_end, table_name], r => r.rowCount) - .then(rowCount => { - console.log('inserted rowcount: ', rowCount) - return Promise.resolve(rowCount == 1) - }).catch(err => { - console.log(err); - return Promise.resolve(false); - }); - } - - public static async createNewStorageTable(tableName: string): Promise { - - // primary key should prevent duplicates by skey per inbox - await PgUtil.update(` + console.log(sql) + return pgPool + .result(sql, [namespace, namespaceShardId, ts_start, ts_end, table_name], (r) => r.rowCount) + .then((rowCount) => { + console.log('inserted rowcount: ', rowCount) + return Promise.resolve(rowCount == 1) + }) + .catch((err) => { + console.log(err) + return Promise.resolve(false) + }) + } + + public static async createNewStorageTable(tableName: string): Promise { + // primary key should prevent duplicates by skey per inbox + await PgUtil.update(` CREATE TABLE IF NOT EXISTS ${tableName} ( namespace VARCHAR(20) NOT NULL, @@ -176,156 +185,197 @@ END $$ LANGUAGE plpgsql; dataSchema VARCHAR(20) NOT NULL, payload JSONB, PRIMARY KEY(namespace,namespace_shard_id,namespace_id,skey) - );`); + );`) - await PgUtil.update(`CREATE INDEX IF NOT EXISTS + await PgUtil.update(`CREATE INDEX IF NOT EXISTS ${tableName}_idx ON ${tableName} - USING btree (namespace ASC, namespace_id ASC, ts ASC);`); - } - - // todo fix params substitution for the pg library; - public static async checkThatShardIsOnThisNode(namespace: string, namespaceShardId: number, nodeId: string): Promise { - const sql = `SELECT count(*) FROM network_storage_layout + USING btree (namespace ASC, namespace_id ASC, ts ASC);`) + } + + // todo fix params substitution for the pg library; + public static async checkThatShardIsOnThisNode( + namespace: string, + namespaceShardId: number, + nodeId: string + ): Promise { + const sql = `SELECT count(*) FROM network_storage_layout where namespace='${namespace}' and namespace_shard_id='${namespaceShardId}' and node_id='${nodeId}'` - console.log(sql); - return pgPool.query(sql).then(data => { - console.log(data) - let cnt = parseInt(data[0].count); - console.log(cnt); - return cnt === 1 - }).catch(err => { - console.log(err); - return Promise.resolve(false); - }); - } - - public static async findStorageTableByDate(namespace: string, namespaceShardId: number, dateYmd: DateTime): Promise { - log.debug(`date is ${dateYmd.toISO()}`); - const sql = `select table_name from node_storage_layout + console.log(sql) + return pgPool + .query(sql) + .then((data) => { + console.log(data) + const cnt = parseInt(data[0].count) + console.log(cnt) + return cnt === 1 + }) + .catch((err) => { + console.log(err) + return Promise.resolve(false) + }) + } + + public static async findStorageTableByDate( + namespace: string, + namespaceShardId: number, + dateYmd: DateTime + ): Promise { + log.debug(`date is ${dateYmd.toISO()}`) + const sql = `select table_name from node_storage_layout where namespace='${namespace}' and namespace_shard_id='${namespaceShardId}' and ts_start <= '${dateYmd.toISO()}' and ts_end > '${dateYmd.toISO()}'` - log.debug(sql); - return pgPool.query(sql).then(data => { - log.debug(data); - if (data.length != 1) { - return Promise.reject('missing table with the correct name'); - } - return data[0].table_name; - }).catch(err => { - log.debug(err); - return Promise.resolve(''); - }); - } - - public static async findValueInTable(tableName: string, skey: string): Promise { - log.debug(`tableName is ${tableName} , skey is ${skey}`); - const sql = `select payload + log.debug(sql) + return pgPool + .query(sql) + .then((data) => { + log.debug(data) + if (data.length != 1) { + return Promise.reject('missing table with the correct name') + } + return data[0].table_name + }) + .catch((err) => { + log.debug(err) + return Promise.resolve('') + }) + } + + public static async findValueInTable(tableName: string, skey: string): Promise { + log.debug(`tableName is ${tableName} , skey is ${skey}`) + const sql = `select payload from ${tableName} - where skey = '${skey}'`; - log.debug(sql); - return pgPool.query(sql).then(data => { - log.debug(data); - if (data.length != 1) { - return Promise.reject('missing table with the correct name'); - } - log.debug(`data found: ${JSON.stringify(data[0].payload)}`) - return data[0].payload; - }).catch(err => { - log.debug(err); - return Promise.resolve(''); - }); - } - - public static async findStorageItem(ns: string, nsIndex: string, tableName: string, skey: string): Promise { - log.debug(`tableName is ${tableName} , skey is ${skey}`); - const sql = `select skey as skey, + where skey = '${skey}'` + log.debug(sql) + return pgPool + .query(sql) + .then((data) => { + log.debug(data) + if (data.length != 1) { + return Promise.reject('missing table with the correct name') + } + log.debug(`data found: ${JSON.stringify(data[0].payload)}`) + return data[0].payload + }) + .catch((err) => { + log.debug(err) + return Promise.resolve('') + }) + } + + public static async findStorageItem( + ns: string, + nsIndex: string, + tableName: string, + skey: string + ): Promise { + log.debug(`tableName is ${tableName} , skey is ${skey}`) + const sql = `select skey as skey, extract(epoch from ts) as ts, payload as payload from ${tableName} - where skey = '${skey}' and namespace_id='${nsIndex}' and namespace='${ns}'`; - log.debug(sql); - return pgPool.query(sql).then(data => { - log.debug(data); - if (data.length != 1) { - return Promise.reject('missing table with the correct name'); - } - let row1 = data[0]; - log.debug(`data found: ${JSON.stringify(row1.payload)}`) - let record = new StorageRecord(ns, row1.skey, row1.ts, row1.payload); - return [record]; - }).catch(err => { - log.debug(err); - return Promise.resolve([]); - }); - } - - static async putValueInTable(ns: string, shardId: number, nsIndex: string, - storageTable: string, ts: string, skey: string, body: string) { - log.debug(`putValueInTable() namespace=${ns}, namespaceShardId=${shardId} - ,storageTable=${storageTable}, skey=${skey}, jsonValue=${body}`); - const sql = `INSERT INTO ${storageTable} (namespace, namespace_shard_id, namespace_id, ts, skey, dataschema, payload) - values (\${ns}, \${shardId}, \${nsIndex}, to_timestamp(\${ts}), \${skey}, 'v1', \${body}) - ON CONFLICT (namespace, namespace_shard_id, namespace_id, skey) DO UPDATE SET payload = \${body}`; - const params = { - ns, - shardId, - nsIndex, - ts, - skey, - body + where skey = '${skey}' and namespace_id='${nsIndex}' and namespace='${ns}'` + log.debug(sql) + return pgPool + .query(sql) + .then((data) => { + log.debug(data) + if (data.length != 1) { + return Promise.reject('missing table with the correct name') } - console.log(sql, params); - return pgPool.none(sql, params).then(data => { - log.debug(data); - return Promise.resolve(); - }).catch(err => { - log.debug(err); - return Promise.reject(err); - }); + const row1 = data[0] + log.debug(`data found: ${JSON.stringify(row1.payload)}`) + const record = new StorageRecord(ns, row1.skey, row1.ts, row1.payload) + return [record] + }) + .catch((err) => { + log.debug(err) + return Promise.resolve([]) + }) + } + + static async putValueInTable( + ns: string, + shardId: number, + nsIndex: string, + storageTable: string, + ts: string, + skey: string, + body: string + ) { + log.debug(`putValueInTable() namespace=${ns}, namespaceShardId=${shardId} + ,storageTable=${storageTable}, skey=${skey}, jsonValue=${body}`) + const sql = `INSERT INTO ${storageTable} (namespace, namespace_shard_id, namespace_id, ts, skey, dataschema, payload) + values (\${ns}, \${shardId}, \${nsIndex}, to_timestamp(\${ts}), \${skey}, 'v1', \${body}) + ON CONFLICT (namespace, namespace_shard_id, namespace_id, skey) DO UPDATE SET payload = \${body}` + const params = { + ns, + shardId, + nsIndex, + ts, + skey, + body } - - static async listInbox(namespace: string, namespaceShardId: number, nsIndex:string, - storageTable: string, firstTsExcluded: string, pageSize:number): Promise { - const pageLookAhead = 3; - const pageSizeForSameTimestamp = pageSize * 20; - const isFirstQuery = StrUtil.isEmpty(firstTsExcluded); - const sql = `select skey as skey, + console.log(sql, params) + return pgPool + .none(sql, params) + .then((data) => { + log.debug(data) + return Promise.resolve() + }) + .catch((err) => { + log.debug(err) + return Promise.reject(err) + }) + } + + static async listInbox( + namespace: string, + namespaceShardId: number, + nsIndex: string, + storageTable: string, + firstTsExcluded: string, + pageSize: number + ): Promise { + const pageLookAhead = 3 + const pageSizeForSameTimestamp = pageSize * 20 + const isFirstQuery = StrUtil.isEmpty(firstTsExcluded) + const sql = `select skey as skey, extract(epoch from ts) as ts, payload as payload from ${storageTable} where namespace='${namespace}' and namespace_id='${nsIndex}' - ${ isFirstQuery ? '' : `and ts > to_timestamp(${firstTsExcluded})` } + ${isFirstQuery ? '' : `and ts > to_timestamp(${firstTsExcluded})`} order by ts - limit ${pageSize + pageLookAhead}`; - log.debug(sql); - let data1 = await pgPool.any(sql); - var items = new Map(); - var lastTs: number = 0; - for (let i = 0; i < Math.min(data1.length, pageSize); i++) { - const item = DbHelper.convertRowToItem(data1[i], namespace); - items.set(item.skey, item); - lastTs = data1[i].ts; + limit ${pageSize + pageLookAhead}` + log.debug(sql) + const data1 = await pgPool.any(sql) + const items = new Map() + let lastTs: number = 0 + for (let i = 0; i < Math.min(data1.length, pageSize); i++) { + const item = DbHelper.convertRowToItem(data1[i], namespace) + items.set(item.skey, item) + lastTs = data1[i].ts + } + log.debug(`added ${items.size} items; lastTs=${lastTs}`) + // [0...{pagesize-1 (lastTs)}...{data1.length-1 (lastTsRowId)}....] + // we always request pageSize+3 rows; so if we have these additional rows we can verify that their ts != last row ts, + // otherwise we should add these additional rows to the output (works only for 2..3 rows) + // otherwise we should execute and additional page request + let lastTsRowId = pageSize - 1 + if (data1.length > pageSize) { + // add extra rows for ts = lastTs + for (let i = pageSize; i < data1.length; i++) { + if (data1[i].ts == lastTs) { + lastTsRowId = i + } else { + break } - log.debug(`added ${items.size} items; lastTs=${lastTs}`) - // [0...{pagesize-1 (lastTs)}...{data1.length-1 (lastTsRowId)}....] - // we always request pageSize+3 rows; so if we have these additional rows we can verify that their ts != last row ts, - // otherwise we should add these additional rows to the output (works only for 2..3 rows) - // otherwise we should execute and additional page request - var lastTsRowId = pageSize - 1; - if (data1.length > pageSize) { - // add extra rows for ts = lastTs - for (let i = pageSize; i < data1.length; i++) { - if (data1[i].ts == lastTs) { - lastTsRowId = i; - } else { - break; - } - } - if (lastTsRowId == data1.length - 1) { - // we have more rows with same timestamp, they won't fit in pageSize+pageLookAhead rows - // let's peform additional select for ts = lastTs - const sql2 = `select skey as skey, + } + if (lastTsRowId == data1.length - 1) { + // we have more rows with same timestamp, they won't fit in pageSize+pageLookAhead rows + // let's peform additional select for ts = lastTs + const sql2 = `select skey as skey, extract(epoch from ts) as ts, payload as payload from ${storageTable} @@ -333,71 +383,79 @@ END $$ LANGUAGE plpgsql; and namespace_id='${nsIndex}' and ts = to_timestamp(${lastTs}) order by ts - limit ${pageSizeForSameTimestamp}`; - log.debug(sql2); - let data2 = await pgPool.any(sql2); - for (let row of data2) { - const item = DbHelper.convertRowToItem(row, namespace); - items.set(item.skey, item); - } - log.debug(`extra query with ${data2.length} items to fix duplicate timestamps pagination, total size is ${items.length}`); - } else if (lastTsRowId > pageSize - 1) { - // we have more rows with same timestamp, they fit in pageSize+pageLookAhead rows - for (let i = pageSize; i <= lastTsRowId; i++) { - const item = DbHelper.convertRowToItem(data1[i], namespace); - items.set(item.skey, item); - } - log.debug(`updated to ${items.size} items to fix duplicate timestamps pagination`) - } + limit ${pageSizeForSameTimestamp}` + log.debug(sql2) + const data2 = await pgPool.any(sql2) + for (const row of data2) { + const item = DbHelper.convertRowToItem(row, namespace) + items.set(item.skey, item) + } + log.debug( + `extra query with ${data2.length} items to fix duplicate timestamps pagination, total size is ${items.length}` + ) + } else if (lastTsRowId > pageSize - 1) { + // we have more rows with same timestamp, they fit in pageSize+pageLookAhead rows + for (let i = pageSize; i <= lastTsRowId; i++) { + const item = DbHelper.convertRowToItem(data1[i], namespace) + items.set(item.skey, item) } - let itemsArr = [...items.values()]; - return { - 'items': itemsArr, - 'lastTs': lastTs - }; + log.debug(`updated to ${items.size} items to fix duplicate timestamps pagination`) + } } - - private static convertRowToItem(rowObj: any, namespace: string) { - return { - ns: namespace, - skey: rowObj.skey, - ts: rowObj.ts, - payload: rowObj.payload - }; + const itemsArr = [...items.values()] + return { + items: itemsArr, + lastTs: lastTs } - - static async listAllNsIndex() { - const updateViewIfNeeded = `select update_storage_all_namespace_view();`; - log.debug(updateViewIfNeeded); - await pgPool.any(updateViewIfNeeded); - const selectAll = `select namespace_id, last_usage from storage_all_namespace_view;`; - log.debug(selectAll); - return pgPool.manyOrNone(selectAll).then(data => { - log.debug(data); - return Promise.resolve(data == null ? [] : data.map(function (item) { - return { - 'nsId': item.namespace_id, - 'last_usage' : item.last_usage - }; - })); - }).catch(err => { - log.debug(err); - return Promise.reject(err); - }); + } + + private static convertRowToItem(rowObj: any, namespace: string) { + return { + ns: namespace, + skey: rowObj.skey, + ts: rowObj.ts, + payload: rowObj.payload } + } + + static async listAllNsIndex() { + const updateViewIfNeeded = `select update_storage_all_namespace_view();` + log.debug(updateViewIfNeeded) + await pgPool.any(updateViewIfNeeded) + const selectAll = `select namespace_id, last_usage from storage_all_namespace_view;` + log.debug(selectAll) + return pgPool + .manyOrNone(selectAll) + .then((data) => { + log.debug(data) + return Promise.resolve( + data == null + ? [] + : data.map(function (item) { + return { + nsId: item.namespace_id, + last_usage: item.last_usage + } + }) + ) + }) + .catch((err) => { + log.debug(err) + return Promise.reject(err) + }) + } } export class StorageRecord { - ns: string; - skey: string; - ts: string; - payload: any; - - - constructor(ns: string, skey: string, ts: string, payload: any) { - this.ns = ns; - this.skey = skey; - this.ts = ts; - this.payload = payload; - } -} \ No newline at end of file + ns: string + skey: string + ts: string + payload: any + + constructor(ns: string, skey: string, ts: string, payload: any) { + this.ns = ns + this.skey = skey + this.ts = ts + this.payload = payload + } +} diff --git a/src/loaders/express.ts b/src/loaders/express.ts index 8a7b429..e32c66c 100755 --- a/src/loaders/express.ts +++ b/src/loaders/express.ts @@ -1,34 +1,32 @@ -import express from 'express'; -const bodyParser = require('body-parser'); -const cors = require('cors'); -import routes from '../api/index'; -import config from '../config/index'; +import express from 'express' +const bodyParser = require('body-parser') +const cors = require('cors') +import routes from '../api/index' export default ({ app }: { app: express.Application }) => { - app.get('/status', (req, res) => { - - res.status(200).end(); - }); + res.status(200).end() + }) app.head('/status', (req, res) => { - res.status(200).end(); - }); + // + res.status(200).end() + }) // The magic package that prevents frontend developers going nuts // Alternate description: // Enable Cross Origin Resource Sharing to all origins by default - app.use(cors()); + app.use(cors()) - app.use('/api', routes()); + app.use('/api', routes()) // Load Static Files // app.use(express.static(config.staticServePath)); /// catch 404 and forward to error handler app.use((req, res, next) => { - const err = new Error('Not Found'); - err['status'] = 404; - next(err); - }); + const err = new Error('Not Found') + err['status'] = 404 + next(err) + }) /// error handlers app.use((err, req, res, next) => { @@ -36,20 +34,16 @@ export default ({ app }: { app: express.Application }) => { * Handle 401 thrown by express-jwt library */ if (err.name === 'UnauthorizedError') { - return res - .status(err.status) - .send({ message: err.message }) - .end(); + return res.status(err.status).send({ message: err.message }).end() } - return next(err); - }); + return next(err) + }) app.use((err, req, res) => { - res.status(err.status || 500); + res.status(err.status || 500) res.json({ error: { info: err.message } - }); - }); - -}; + }) + }) +} diff --git a/src/loaders/index.ts b/src/loaders/index.ts index feef245..40d43d2 100755 --- a/src/loaders/index.ts +++ b/src/loaders/index.ts @@ -1,23 +1,21 @@ -import expressLoader from './express'; +import expressLoader from './express' // import dependencyInjectorLoader from './dependencyInjector'; - -import logger from './logger'; +import logger from './logger' // import initializer from './initializer'; // import dbLoader from './db'; // import dbListenerLoader from './dbListener'; - //We have to import at least all the events once so they can be triggered // import './events'; export default async ({ expressApp, server, testMode }) => { - logger.info('loaders init'); + logger.info('loaders init') // await dependencyInjectorLoader(); - await expressLoader({ app: expressApp }); + await expressLoader({ app: expressApp }) // await socketLoader({ server: server }); -}; +} diff --git a/src/loaders/logger.ts b/src/loaders/logger.ts index 752b321..bce3c7f 100755 --- a/src/loaders/logger.ts +++ b/src/loaders/logger.ts @@ -1,7 +1,7 @@ -import winston from 'winston'; -import config from '../config'; -import {WinstonUtil} from "../utilz/winstonUtil"; +import winston from 'winston' +import config from '../config' +import { WinstonUtil } from '../utilz/winstonUtil' const customLevels = { levels: { @@ -14,7 +14,7 @@ const customLevels = { saved: 6, verbose: 7, debug: 8, - silly: 9, + silly: 9 }, colors: { info: 'green', @@ -23,16 +23,16 @@ const customLevels = { saved: 'italic white', debug: 'yellow' } -}; +} -let transports = []; +const transports = [] transports.push( // Console should always be at 0 and dynamic log should always be at 2 // remember and not change it as it's manually baked in hijackLogger WinstonUtil.consoleTransport, WinstonUtil.debugFileTransport, - WinstonUtil.errorFileTransport, -); + WinstonUtil.errorFileTransport +) // WE SIMPLY REDIRECT ALL TO winstonUtil formatter x winstonUtil transports // this instance is being used across the whole codebase const LoggerInstance = winston.createLogger({ @@ -40,8 +40,8 @@ const LoggerInstance = winston.createLogger({ levels: customLevels.levels, format: WinstonUtil.createFormat2WhichRendersClassName(), transports -}); +}) -winston.addColors(customLevels.colors); +winston.addColors(customLevels.colors) -export default LoggerInstance; \ No newline at end of file +export default LoggerInstance diff --git a/src/services/messaging-common/messageBlock.ts b/src/services/messaging-common/messageBlock.ts index 1a4953b..2179f46 100644 --- a/src/services/messaging-common/messageBlock.ts +++ b/src/services/messaging-common/messageBlock.ts @@ -9,15 +9,16 @@ { s1, 0xD}, { s2, 0xE}, { s3, 0xF}, { d1, 0x1}, { d2, 0x2}, { d3, 0x3} ] */ -import {Coll} from '../../utilz/coll' +import { Logger } from 'winston' + +import { Check } from '../../utilz/check' +import { Coll } from '../../utilz/coll' +import { EthSig } from '../../utilz/ethSig' +import { EthUtil } from '../../utilz/EthUtil' +import { NumUtil } from '../../utilz/numUtil' +import { ObjectHasher } from '../../utilz/objectHasher' import StrUtil from '../../utilz/strUtil' -import {EthSig} from '../../utilz/ethSig' -import {Logger} from 'winston' -import {WinstonUtil} from '../../utilz/winstonUtil' -import {ObjectHasher} from '../../utilz/objectHasher' -import {EthUtil} from '../../utilz/EthUtil' -import {Check} from '../../utilz/check' -import {NumUtil} from "../../utilz/numUtil"; +import { WinstonUtil } from '../../utilz/winstonUtil' /* ex: @@ -345,10 +346,10 @@ export class MessageBlockUtil { const shards = new Set() for (const fi of block.responses) { for (const recipient of fi.header.recipientsResolved) { - let shardId = this.calculateAffectedShard(recipient.addr, shardCount); + const shardId = this.calculateAffectedShard(recipient.addr, shardCount) if (shardId == null) { - this.log.error('cannot calculate shardId for recipient %o in %o', recipient, fi); - continue; + this.log.error('cannot calculate shardId for recipient %o in %o', recipient, fi) + continue } shards.add(shardId) } @@ -364,35 +365,36 @@ export class MessageBlockUtil { // lets read this value from a contract public static calculateAffectedShard(recipientAddr: string, shardCount: number): number | null { if (StrUtil.isEmpty(recipientAddr)) { - return null; + return null } let shardId: number = null const addrObj = EthUtil.parseCaipAddress(recipientAddr) - if (addrObj != null - && !StrUtil.isEmpty(addrObj.addr) - && addrObj.addr.startsWith('0x') - && addrObj.addr.length > 4) { - const firstByteAsHex = addrObj.addr.substring(2, 4).toLowerCase(); - shardId = Number.parseInt(firstByteAsHex, 16); + if ( + addrObj != null && + !StrUtil.isEmpty(addrObj.addr) && + addrObj.addr.startsWith('0x') && + addrObj.addr.length > 4 + ) { + const firstByteAsHex = addrObj.addr.substring(2, 4).toLowerCase() + shardId = Number.parseInt(firstByteAsHex, 16) } // 2) try to get sha256 otherwise if (shardId == null) { - const firstByteAsHex = ObjectHasher.hashToSha256(recipientAddr).toLowerCase().substring(0, 2); - shardId = Number.parseInt(firstByteAsHex, 16); + const firstByteAsHex = ObjectHasher.hashToSha256(recipientAddr).toLowerCase().substring(0, 2) + shardId = Number.parseInt(firstByteAsHex, 16) } - Check.notNull(shardId); - Check.isTrue(shardId >= 0 && shardId <= 255 && NumUtil.isRoundedInteger(shardId)); - Check.isTrue(shardCount >= 1); - return shardId % shardCount; + Check.notNull(shardId) + Check.isTrue(shardId >= 0 && shardId <= 255 && NumUtil.isRoundedInteger(shardId)) + Check.isTrue(shardCount >= 1) + return shardId % shardCount } public static getBlockCreationTimeMillis(block: MessageBlock): number | null { - if (block.responsesSignatures.length > 0 - && block.responsesSignatures[0].length > 0) { - const sig = block.responsesSignatures[0][0]; - return sig?.nodeMeta?.tsMillis; + if (block.responsesSignatures.length > 0 && block.responsesSignatures[0].length > 0) { + const sig = block.responsesSignatures[0][0] + return sig?.nodeMeta?.tsMillis } - return null; + return null } public static checkBlock(block: MessageBlock, validatorsFromContract: Set): CheckResult { @@ -463,10 +465,10 @@ export class CheckResult { err: string static failWithText(err: string): CheckResult { - return {success: false, err: err} + return { success: false, err: err } } static ok(): CheckResult { - return {success: true, err: ''} + return { success: true, err: '' } } } diff --git a/src/services/messaging-common/queueClientHelper.ts b/src/services/messaging-common/queueClientHelper.ts index e97eb90..95ccce9 100644 --- a/src/services/messaging-common/queueClientHelper.ts +++ b/src/services/messaging-common/queueClientHelper.ts @@ -1,8 +1,9 @@ +import { Logger } from 'winston' + import { Check } from '../../utilz/check' -import { ValidatorContractState } from './validatorContractState' import { MySqlUtil } from '../../utilz/mySqlUtil' -import { Logger } from 'winston' import { WinstonUtil } from '../../utilz/winstonUtil' +import { ValidatorContractState } from './validatorContractState' export class QueueClientHelper { private static log: Logger = WinstonUtil.newLog(QueueClientHelper) diff --git a/src/services/messaging-common/redisClient.ts b/src/services/messaging-common/redisClient.ts index b765299..974b767 100644 --- a/src/services/messaging-common/redisClient.ts +++ b/src/services/messaging-common/redisClient.ts @@ -1,8 +1,9 @@ import { createClient, RedisClientType } from 'redis' -import config from '../../config' +import { Service } from 'typedi' import { Logger } from 'winston' + +import config from '../../config' import { WinstonUtil } from '../../utilz/winstonUtil' -import { Service } from 'typedi' @Service() export class RedisClient { diff --git a/src/services/messaging-common/storageContractState.ts b/src/services/messaging-common/storageContractState.ts index 2d9f27a..5b987c6 100644 --- a/src/services/messaging-common/storageContractState.ts +++ b/src/services/messaging-common/storageContractState.ts @@ -1,151 +1,157 @@ -import {Service} from "typedi"; -import {Logger} from "winston"; -import {WinstonUtil} from "../../utilz/winstonUtil"; -import {EnvLoader} from "../../utilz/envLoader"; -import {Contract, ethers, Wallet} from "ethers"; -import {JsonRpcProvider} from "@ethersproject/providers/src.ts/json-rpc-provider"; -import {readFileSync} from "fs"; -import {BitUtil} from "../../utilz/bitUtil"; -import {EthersUtil} from "../../utilz/ethersUtil"; -import {Coll} from "../../utilz/coll"; -import {Check} from "../../utilz/check"; - +import { JsonRpcProvider } from '@ethersproject/providers/src.ts/json-rpc-provider' +import { Contract, ethers, Wallet } from 'ethers' +import { Service } from 'typedi' +import { Logger } from 'winston' + +import { BitUtil } from '../../utilz/bitUtil' +import { Check } from '../../utilz/check' +import { Coll } from '../../utilz/coll' +import { EnvLoader } from '../../utilz/envLoader' +import { EthersUtil } from '../../utilz/ethersUtil' +import { WinstonUtil } from '../../utilz/winstonUtil' @Service() export class StorageContractState { - public log: Logger = WinstonUtil.newLog(StorageContractState); + public log: Logger = WinstonUtil.newLog(StorageContractState) - private listener: StorageContractListener; + private listener: StorageContractListener private provider: JsonRpcProvider private rpcEndpoint: string private rpcNetwork: number - private useSigner: boolean; + private useSigner: boolean // NODE STATE - private nodeWallet: Wallet | null; - private nodeAddress: string | null; + private nodeWallet: Wallet | null + private nodeAddress: string | null // CONTRACT STATE private storageCtAddr: string - private storageCt: StorageContract; - public rf: number; - public shardCount: number; + private storageCt: StorageContract + public rf: number + public shardCount: number // node0xA -> shard0, shard1, shard2 - public nodeShardMap: Map> = new Map(); + public nodeShardMap: Map> = new Map() // VARS // shard0 -> node0xA, node0xB - public shardToNodesMap: Map> = new Map(); + public shardToNodesMap: Map> = new Map() public async postConstruct(useSigner: boolean, listener: StorageContractListener) { - this.log.info('postConstruct()'); - this.listener = listener; + this.log.info('postConstruct()') + this.listener = listener this.storageCtAddr = EnvLoader.getPropertyOrFail('STORAGE_CONTRACT_ADDRESS') this.rpcEndpoint = EnvLoader.getPropertyOrFail('VALIDATOR_RPC_ENDPOINT') this.rpcNetwork = Number.parseInt(EnvLoader.getPropertyOrFail('VALIDATOR_RPC_NETWORK')) - this.provider = new ethers.providers.JsonRpcProvider( - this.rpcEndpoint, - this.rpcNetwork - ) - this.useSigner = useSigner; + this.provider = new ethers.providers.JsonRpcProvider(this.rpcEndpoint, this.rpcNetwork) + this.useSigner = useSigner if (useSigner) { - let connect = await EthersUtil.connectWithKey( + const connect = await EthersUtil.connectWithKey( EnvLoader.getPropertyOrFail('CONFIG_DIR'), EnvLoader.getPropertyOrFail('VALIDATOR_PRIVATE_KEY_FILE'), EnvLoader.getPropertyOrFail('VALIDATOR_PRIVATE_KEY_PASS'), 'StorageV1.json', EnvLoader.getPropertyOrFail('STORAGE_CONTRACT_ADDRESS'), this.provider - ); - this.storageCt = connect.contract; - this.nodeWallet = connect.nodeWallet; - this.nodeAddress = connect.nodeAddress; + ) + this.storageCt = connect.contract + this.nodeWallet = connect.nodeWallet + this.nodeAddress = connect.nodeAddress } else { - this.storageCt = await EthersUtil.connectWithoutKey( - EnvLoader.getPropertyOrFail('CONFIG_DIR'), - 'StorageV1.json', - EnvLoader.getPropertyOrFail('STORAGE_CONTRACT_ADDRESS'), - this.provider + this.storageCt = ( + await EthersUtil.connectWithoutKey( + EnvLoader.getPropertyOrFail('CONFIG_DIR'), + 'StorageV1.json', + EnvLoader.getPropertyOrFail('STORAGE_CONTRACT_ADDRESS'), + this.provider + ) ) } - await this.readContractState(); - await this.subscribeToContractChanges(); // todo ? ethers or hardhat always emits 1 fake event + await this.readContractState() + await this.subscribeToContractChanges() // todo ? ethers or hardhat always emits 1 fake event } public async readContractState() { this.log.info(`connected to StorageContract`) - this.rf = await this.storageCt.rf(); - let nodeCount = await this.storageCt.nodeCount(); - this.shardCount = await this.storageCt.SHARD_COUNT(); - this.log.info(`rf: ${this.rf} , shard count: ${this.shardCount} total nodeCount: ${nodeCount}`); - let nodesAddrList = await this.storageCt.getNodeAddresses(); + this.rf = await this.storageCt.rf() + const nodeCount = await this.storageCt.nodeCount() + this.shardCount = await this.storageCt.SHARD_COUNT() + this.log.info(`rf: ${this.rf} , shard count: ${this.shardCount} total nodeCount: ${nodeCount}`) + const nodesAddrList = await this.storageCt.getNodeAddresses() - await this.reloadEveryAddressAndNotifyListeners(nodesAddrList); + await this.reloadEveryAddressAndNotifyListeners(nodesAddrList) } public async subscribeToContractChanges() { this.storageCt.on('SNodeMappingChanged', async (nodeList: string[]) => { - this.log.info(`EVENT: SNodeMappingChanged: nodeList=${JSON.stringify(nodeList)}`); - await this.reloadEveryAddressAndNotifyListeners(nodeList); - }); + this.log.info(`EVENT: SNodeMappingChanged: nodeList=${JSON.stringify(nodeList)}`) + await this.reloadEveryAddressAndNotifyListeners(nodeList) + }) } // todo we can add 1 contract call for all addrs async reloadEveryAddressAndNotifyListeners(nodeAddrList: string[]): Promise { for (const nodeAddr of nodeAddrList) { - let nodeShardmask = await this.storageCt.getNodeShardsByAddr(nodeAddr); - const shardSet = Coll.arrayToSet(BitUtil.bitsToPositions(nodeShardmask)); - this.nodeShardMap.set(nodeAddr, shardSet); - this.log.info(`node %s is re-assigned to shards (%s) : %s`, - nodeAddr, nodeShardmask.toString(2), Coll.setToArray(shardSet)); + const nodeShardmask = await this.storageCt.getNodeShardsByAddr(nodeAddr) + const shardSet = Coll.arrayToSet(BitUtil.bitsToPositions(nodeShardmask)) + this.nodeShardMap.set(nodeAddr, shardSet) + this.log.info( + `node %s is re-assigned to shards (%s) : %s`, + nodeAddr, + nodeShardmask.toString(2), + Coll.setToArray(shardSet) + ) } - this.shardToNodesMap.clear(); + this.shardToNodesMap.clear() for (const [nodeAddr, shardSet] of this.nodeShardMap) { for (const shard of shardSet) { - let nodes = this.shardToNodesMap.get(shard); + let nodes = this.shardToNodesMap.get(shard) if (nodes == null) { - nodes = new Set(); - this.shardToNodesMap.set(shard, nodes); + nodes = new Set() + this.shardToNodesMap.set(shard, nodes) } - nodes.add(nodeAddr); + nodes.add(nodeAddr) } } - let nodeShards: Set = null; + let nodeShards: Set = null if (this.useSigner) { - this.log.info(`this node %s is assigned to shards (%s) : %s`, + this.log.info( + `this node %s is assigned to shards (%s) : %s`, this.nodeAddress, - Coll.setToArray(this.getNodeShards())); - nodeShards = this.getNodeShards(); + Coll.setToArray(this.getNodeShards()) + ) + nodeShards = this.getNodeShards() } - await this.listener.handleReshard(nodeShards, this.nodeShardMap); + await this.listener.handleReshard(nodeShards, this.nodeShardMap) } // fails if this.nodeAddress is not defined public getNodeShards(): Set { - Check.notEmpty(this.nodeAddress); - const nodeShards = this.nodeShardMap.get(this.nodeAddress); - Check.notEmptySet(nodeShards); - return nodeShards; + Check.notEmpty(this.nodeAddress) + const nodeShards = this.nodeShardMap.get(this.nodeAddress) + Check.notEmptySet(nodeShards) + return nodeShards } public getStorageNodesForShard(shard: number): Set | null { - return this.shardToNodesMap.get(shard); + return this.shardToNodesMap.get(shard) } } -type StorageContract = StorageContractAPI & Contract; +type StorageContract = StorageContractAPI & Contract export interface StorageContractAPI { + rf(): Promise - rf(): Promise; + getNodeAddresses(): Promise - getNodeAddresses(): Promise; + getNodeShardsByAddr(addr: string): Promise - getNodeShardsByAddr(addr: string): Promise; + nodeCount(): Promise - nodeCount(): Promise; - - SHARD_COUNT(): Promise; + SHARD_COUNT(): Promise } export interface StorageContractListener { - handleReshard(currentNodeShards: Set | null, allNodeShards: Map>): Promise; -} \ No newline at end of file + handleReshard( + currentNodeShards: Set | null, + allNodeShards: Map> + ): Promise +} diff --git a/src/services/messaging-common/validatorContractState.ts b/src/services/messaging-common/validatorContractState.ts index 0a8f7e5..347c2bc 100644 --- a/src/services/messaging-common/validatorContractState.ts +++ b/src/services/messaging-common/validatorContractState.ts @@ -1,12 +1,12 @@ -import { Service } from 'typedi' +import { JsonRpcProvider } from '@ethersproject/providers/src.ts/json-rpc-provider' import { Contract, ethers, Wallet } from 'ethers' -import StrUtil from '../../utilz/strUtil' - import fs, { readFileSync } from 'fs' import path from 'path' -import { JsonRpcProvider } from '@ethersproject/providers/src.ts/json-rpc-provider' -import { EnvLoader } from '../../utilz/envLoader' +import { Service } from 'typedi' import { Logger } from 'winston' + +import { EnvLoader } from '../../utilz/envLoader' +import StrUtil from '../../utilz/strUtil' import { WinstonUtil } from '../../utilz/winstonUtil' /* diff --git a/src/services/messaging-dset/queueClient.ts b/src/services/messaging-dset/queueClient.ts index 7443f52..7381261 100644 --- a/src/services/messaging-dset/queueClient.ts +++ b/src/services/messaging-dset/queueClient.ts @@ -1,10 +1,11 @@ // ------------------------------ // reads other node queue fully, appends everything to the local queue/storage -import {Logger} from 'winston' -import {WinstonUtil} from '../../utilz/winstonUtil' -import {MySqlUtil} from '../../utilz/mySqlUtil' import axios from 'axios' -import {Consumer, QItem} from './queueTypes' +import { Logger } from 'winston' + +import { MySqlUtil } from '../../utilz/mySqlUtil' +import { WinstonUtil } from '../../utilz/winstonUtil' +import { Consumer, QItem } from './queueTypes' export class QueueClient { public log: Logger = WinstonUtil.newLog(QueueClient) @@ -66,12 +67,12 @@ export class QueueClient { } for (const item of reply.items) { endpointStats.downloadedItems++ - let appendSuccessful = false; + let appendSuccessful = false try { - appendSuccessful = await this.consumer.accept(item); + appendSuccessful = await this.consumer.accept(item) } catch (e) { - this.log.error('error processing accept(): queue %s: ', this.queueName); - this.log.error(e); + this.log.error('error processing accept(): queue %s: ', this.queueName) + this.log.error(e) } if (appendSuccessful) { endpointStats.newItems++ @@ -113,7 +114,7 @@ export class QueueClient { public async readLastOffset(queueName: string, baseUri: string): Promise { const url = `${baseUri}/api/v1/dset/queue/${queueName}/lastOffset` - const resp: { result: number } = await axios.get(url, {timeout: 3000}) + const resp: { result: number } = await axios.get(url, { timeout: 3000 }) return resp.result } } diff --git a/src/services/messaging-dset/queueServer.ts b/src/services/messaging-dset/queueServer.ts index 3a5616b..1a770d0 100644 --- a/src/services/messaging-dset/queueServer.ts +++ b/src/services/messaging-dset/queueServer.ts @@ -3,11 +3,12 @@ // this node: appends it - if storage says yes to a new item // other nodes: read this from client import { Logger } from 'winston' -import { WinstonUtil } from '../../utilz/winstonUtil' + import { MySqlUtil } from '../../utilz/mySqlUtil' -import { Consumer, DCmd, QItem } from './queueTypes' -import StrUtil from '../../utilz/strUtil' import { ObjectHasher } from '../../utilz/objectHasher' +import StrUtil from '../../utilz/strUtil' +import { WinstonUtil } from '../../utilz/winstonUtil' +import { Consumer, DCmd, QItem } from './queueTypes' export class QueueServer implements Consumer { private log: Logger = WinstonUtil.newLog(QueueServer) diff --git a/src/services/messaging/BlockStorage.ts b/src/services/messaging/BlockStorage.ts index 4a36685..1209c90 100644 --- a/src/services/messaging/BlockStorage.ts +++ b/src/services/messaging/BlockStorage.ts @@ -1,20 +1,20 @@ -import {Service} from "typedi"; -import {WinstonUtil} from "../../utilz/winstonUtil"; -import {Logger} from "winston"; -import {MySqlUtil} from "../../utilz/mySqlUtil"; -import {MessageBlock, MessageBlockUtil} from "../messaging-common/messageBlock"; -import StrUtil from "../../utilz/strUtil"; -import {Coll} from "../../utilz/coll"; -import {Check} from "../../utilz/check"; -import DbHelper from "../../helpers/dbHelper"; +import { Service } from 'typedi' +import { Logger } from 'winston' + +import { Check } from '../../utilz/check' +import { Coll } from '../../utilz/coll' +import { MySqlUtil } from '../../utilz/mySqlUtil' +import StrUtil from '../../utilz/strUtil' +import { WinstonUtil } from '../../utilz/winstonUtil' +import { MessageBlock } from '../messaging-common/messageBlock' // stores everything in MySQL @Service() export class BlockStorage { - public log: Logger = WinstonUtil.newLog(BlockStorage); + public log: Logger = WinstonUtil.newLog(BlockStorage) public async postConstruct() { - await this.createStorageTablesIfNeeded(); + await this.createStorageTablesIfNeeded() } private async createStorageTablesIfNeeded() { @@ -27,7 +27,7 @@ export class BlockStorage { PRIMARY KEY (object_hash) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; - `); + `) await MySqlUtil.insert(` CREATE TABLE IF NOT EXISTS dset_queue_mblock @@ -41,7 +41,7 @@ export class BlockStorage { FOREIGN KEY (object_hash) REFERENCES blocks (object_hash) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; - `); + `) await MySqlUtil.insert(` CREATE TABLE IF NOT EXISTS dset_client @@ -56,7 +56,7 @@ export class BlockStorage { UNIQUE KEY uniq_dset_name_and_target (queue_name, target_node_id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; - `); + `) await MySqlUtil.insert(` CREATE TABLE IF NOT EXISTS contract_shards @@ -67,40 +67,51 @@ export class BlockStorage { ) ENGINE = InnoDB DEFAULT CHARSET = utf8; - `); + `) } - async saveBlockWithShardData(mb: MessageBlock, calculatedHash: string, shardSet: Set): Promise { + async saveBlockWithShardData( + mb: MessageBlock, + calculatedHash: string, + shardSet: Set + ): Promise { // NOTE: the code already atomically updates the db , // so let's drop select because it's excessive) - let hashFromDb = await MySqlUtil.queryOneValueOrDefault( + const hashFromDb = await MySqlUtil.queryOneValueOrDefault( 'SELECT object_hash FROM blocks where object_hash=?', - null, calculatedHash); + null, + calculatedHash + ) if (hashFromDb != null) { - this.log.info('received block with hash %s, ' + - 'already exists in the storage at index %s, ignoring', - calculatedHash, hashFromDb); - return false; + this.log.info( + 'received block with hash %s, ' + 'already exists in the storage at index %s, ignoring', + calculatedHash, + hashFromDb + ) + return false } // insert block - this.log.info('received block with hash %s, adding to the db', calculatedHash); - const objectAsJson = JSON.stringify(mb); - const shardSetAsJson = JSON.stringify(Coll.setToArray(shardSet)); + this.log.info('received block with hash %s, adding to the db', calculatedHash) + const objectAsJson = JSON.stringify(mb) + const shardSetAsJson = JSON.stringify(Coll.setToArray(shardSet)) const res = await MySqlUtil.insert( `INSERT IGNORE INTO blocks(object, object_hash, object_shards) VALUES (?, ?, ?)`, - objectAsJson, calculatedHash, shardSetAsJson); - let requiresProcessing = res.affectedRows === 1; + objectAsJson, + calculatedHash, + shardSetAsJson + ) + const requiresProcessing = res.affectedRows === 1 if (!requiresProcessing) { - return false; + return false } // insert block to shard mapping - let valuesStr = ''; - let valuesArr = []; + let valuesStr = '' + const valuesArr = [] for (const shardId of shardSet) { - valuesArr.push(calculatedHash, shardId); + valuesArr.push(calculatedHash, shardId) valuesStr += valuesStr.length == 0 ? '(? , ?)' : ',(? , ?)' } const res2 = await MySqlUtil.insert( @@ -109,8 +120,9 @@ export class BlockStorage { INTO dset_queue_mblock(object_hash, object_shard) VALUES ${valuesStr}`, - ...valuesArr); - return true; + ...valuesArr + ) + return true } /** @@ -120,15 +132,17 @@ export class BlockStorage { */ public async loadNodeShards(): Promise | null> { const shardsAssigned = await MySqlUtil.queryOneValueOrDefault( - 'select shards_assigned from contract_shards order by ts desc limit 1', null); - let result: Set; + 'select shards_assigned from contract_shards order by ts desc limit 1', + null + ) + let result: Set if (shardsAssigned != null && !StrUtil.isEmpty(shardsAssigned)) { - const arr = JSON.parse(shardsAssigned); - result = new Set(arr); + const arr = JSON.parse(shardsAssigned) + result = new Set(arr) } else { - result = new Set(); + result = new Set() } - return result; + return result } /** @@ -137,9 +151,12 @@ export class BlockStorage { * @param nodeShards */ public async saveNodeShards(nodeShards: Set): Promise { - let arr = Coll.setToArray(nodeShards); - let arrAsJson = JSON.stringify(arr); - await MySqlUtil.insert('INSERT IGNORE INTO contract_shards(shards_assigned) VALUES(?)', arrAsJson); + const arr = Coll.setToArray(nodeShards) + const arrAsJson = JSON.stringify(arr) + await MySqlUtil.insert( + 'INSERT IGNORE INTO contract_shards(shards_assigned) VALUES(?)', + arrAsJson + ) } /** @@ -154,41 +171,47 @@ export class BlockStorage { * @param shardsToLookFor shards filter * @param handler callback for processing */ - public async iterateAllStoredBlocks(shardCountFromStorageContract: number, - pageSize: number, - shardsToLookFor: Set, - handler: (messageBlockJson: string, - messageBlockHash: string, - messageBlockShards: Set) => Promise) { + public async iterateAllStoredBlocks( + shardCountFromStorageContract: number, + pageSize: number, + shardsToLookFor: Set, + handler: ( + messageBlockJson: string, + messageBlockHash: string, + messageBlockShards: Set + ) => Promise + ) { if (shardsToLookFor.size == 0) { - this.log.debug('iterateAllStoredBlocks(): no shards to unpack '); - return; + this.log.debug('iterateAllStoredBlocks(): no shards to unpack ') + return } // [1,2] => (1, 2) - let shardsAsCsv = Array.from(shardsToLookFor.keys()).join(','); + const shardsAsCsv = Array.from(shardsToLookFor.keys()).join(',') // query size - let queryLimit = pageSize; - Check.isTrue(queryLimit > 0 && queryLimit < 1000, 'bad query limit'); - let subqueryRowLimit = Math.round(3 * pageSize * shardCountFromStorageContract / 2); - Check.isTrue(shardCountFromStorageContract > 0 && shardCountFromStorageContract < 1000, - 'bad subquery limit'); + const queryLimit = pageSize + Check.isTrue(queryLimit > 0 && queryLimit < 1000, 'bad query limit') + const subqueryRowLimit = Math.round((3 * pageSize * shardCountFromStorageContract) / 2) + Check.isTrue( + shardCountFromStorageContract > 0 && shardCountFromStorageContract < 1000, + 'bad subquery limit' + ) // remember last N object hashes; // this is the amount of a page that should be enough to remove duplicate object hashes // i.e. 32 shards x 30 rows = approx 1000 rows of history - let cache = new Set(); - let cacheMaxSize = 2 * subqueryRowLimit; + const cache = new Set() + const cacheMaxSize = 2 * subqueryRowLimit - let fromId = null; - let rows = null; + let fromId = null + let rows = null do { /* Finds rows | minId | object_hash | shardsJson | | 3 | 1 | [2,1] | */ - rows = await MySqlUtil.queryArr<{ minId: number, object_hash: string, shardsJson: string }>( + rows = await MySqlUtil.queryArr<{ minId: number; object_hash: string; shardsJson: string }>( `SELECT MIN(id) as minId, object_hash, CONCAT('[', GROUP_CONCAT(object_shard), ']') as shardsJson FROM (SELECT id, object_hash, object_shard FROM dset_queue_mblock @@ -199,30 +222,37 @@ export class BlockStorage { GROUP BY object_hash ORDER BY minId DESC LIMIT ?`, - fromId, fromId, subqueryRowLimit, queryLimit); + fromId, + fromId, + subqueryRowLimit, + queryLimit + ) for (const row of rows) { - const mbHash = row.object_hash; + const mbHash = row.object_hash if (!cache.has(mbHash)) { - const row = await MySqlUtil.queryOneRow<{ object: string, object_shards: string }>( - 'select object, object_shards from blocks where object_hash=?', mbHash); + const row = await MySqlUtil.queryOneRow<{ object: string; object_shards: string }>( + 'select object, object_shards from blocks where object_hash=?', + mbHash + ) if (row == null || StrUtil.isEmpty(row.object)) { - this.log.error('skipping objectHash=%s because there is no matching shard info in blocks table', - mbHash); - continue; + this.log.error( + 'skipping objectHash=%s because there is no matching shard info in blocks table', + mbHash + ) + continue } - const mbShardSet = Coll.parseAsNumberSet(row.object_shards); - await handler(row.object, mbHash, mbShardSet); - cache.add(mbHash); + const mbShardSet = Coll.parseAsNumberSet(row.object_shards) + await handler(row.object, mbHash, mbShardSet) + cache.add(mbHash) if (cache.size > cacheMaxSize) { - const firstCachedValue = cache.values().next().value; - cache.delete(firstCachedValue); + const firstCachedValue = cache.values().next().value + cache.delete(firstCachedValue) } } - fromId = Math.min(fromId, row.minId); + fromId = Math.min(fromId, row.minId) } - Check.isTrue(cache.size <= cacheMaxSize); - } while (rows != null && rows.length > 0); + Check.isTrue(cache.size <= cacheMaxSize) + } while (rows != null && rows.length > 0) } - -} \ No newline at end of file +} diff --git a/src/services/messaging/IndexStorage.ts b/src/services/messaging/IndexStorage.ts index 68c6d9f..0733f68 100644 --- a/src/services/messaging/IndexStorage.ts +++ b/src/services/messaging/IndexStorage.ts @@ -1,30 +1,29 @@ -import {WinstonUtil} from "../../utilz/winstonUtil"; -import {Inject, Service} from "typedi"; -import {Logger} from "winston"; -import {FPayload, MessageBlock, MessageBlockUtil} from "../messaging-common/messageBlock"; -import {Check} from "../../utilz/check"; -import {Coll} from "../../utilz/coll"; -import DateUtil from "../../utilz/dateUtil"; -import DbHelper from "../../helpers/dbHelper"; -import StrUtil from "../../utilz/strUtil"; -import {StorageContractState} from "../messaging-common/storageContractState"; -import {ValidatorContractState} from "../messaging-common/validatorContractState"; -import {PgUtil} from "../../utilz/pgUtil"; +import { Inject, Service } from 'typedi' +import { Logger } from 'winston' +import DbHelper from '../../helpers/dbHelper' +import { Coll } from '../../utilz/coll' +import DateUtil from '../../utilz/dateUtil' +import { PgUtil } from '../../utilz/pgUtil' +import StrUtil from '../../utilz/strUtil' +import { WinstonUtil } from '../../utilz/winstonUtil' +import { FPayload, MessageBlock, MessageBlockUtil } from '../messaging-common/messageBlock' +import { StorageContractState } from '../messaging-common/storageContractState' +import { ValidatorContractState } from '../messaging-common/validatorContractState' // stores everything in Postgres (!) @Service() export class IndexStorage { - public log: Logger = WinstonUtil.newLog(IndexStorage); + public log: Logger = WinstonUtil.newLog(IndexStorage) @Inject() - private valContractState: ValidatorContractState; + private valContractState: ValidatorContractState @Inject() - private storageContractState: StorageContractState; + private storageContractState: StorageContractState public async postConstruct() { - await DbHelper.createStorageTablesIfNeeded(); + await DbHelper.createStorageTablesIfNeeded() } /* @@ -35,36 +34,45 @@ export class IndexStorage { */ public async unpackBlockToInboxes(mb: MessageBlock, shardSet: Set) { // this is the list of shards that we support on this node - const nodeShards = this.storageContractState.getNodeShards(); - this.log.debug('storage node supports %s shards: %o', nodeShards.size, nodeShards); - let shardsToProcess = Coll.intersectSet(shardSet, nodeShards); + const nodeShards = this.storageContractState.getNodeShards() + this.log.debug('storage node supports %s shards: %o', nodeShards.size, nodeShards) + const shardsToProcess = Coll.intersectSet(shardSet, nodeShards) this.log.debug('block %s has %d inboxes to unpack', mb.id, shardsToProcess) if (shardsToProcess.size == 0) { - this.log.debug('finished'); - return; + this.log.debug('finished') + return } // ex: 1661214142.123456 - let tsString = '' + (MessageBlockUtil.getBlockCreationTimeMillis(mb) / 1000.0); + let tsString = '' + MessageBlockUtil.getBlockCreationTimeMillis(mb) / 1000.0 if (tsString == null) { - tsString = '' + DateUtil.currentTimeSeconds(); + tsString = '' + DateUtil.currentTimeSeconds() } - const currentNodeId = this.valContractState.nodeId; + const currentNodeId = this.valContractState.nodeId for (let i = 0; i < mb.responses.length; i++) { - const feedItem = mb.responses[i]; - const targetWallets: string[] = MessageBlockUtil.calculateRecipients(mb, i); + const feedItem = mb.responses[i] + const targetWallets: string[] = MessageBlockUtil.calculateRecipients(mb, i) for (let i1 = 0; i1 < targetWallets.length; i1++) { - const targetAddr = targetWallets[i1]; - let targetShard = MessageBlockUtil.calculateAffectedShard(targetAddr, this.storageContractState.shardCount); + const targetAddr = targetWallets[i1] + const targetShard = MessageBlockUtil.calculateAffectedShard( + targetAddr, + this.storageContractState.shardCount + ) if (!shardsToProcess.has(targetShard)) { - continue; + continue } - await this.putPayloadToInbox('inbox', targetShard, targetAddr, tsString, currentNodeId, feedItem.payload); + await this.putPayloadToInbox( + 'inbox', + targetShard, + targetAddr, + tsString, + currentNodeId, + feedItem.payload + ) } } } - /** * Puts a single item into * This is POSTGRES @@ -78,60 +86,77 @@ export class IndexStorage { * * todo pass ts as number */ - public async putPayloadToInbox(nsName: string, nsShardId: number, nsId: string, - ts: string, - nodeId: string, - fpayload: FPayload) { - const key = fpayload.data.sid; - fpayload.recipients = null; // null recipients field because we don't need that - const date = DateUtil.parseUnixFloatAsDateTime(ts); + public async putPayloadToInbox( + nsName: string, + nsShardId: number, + nsId: string, + ts: string, + nodeId: string, + fpayload: FPayload + ) { + const key = fpayload.data.sid + fpayload.recipients = null // null recipients field because we don't need that + const date = DateUtil.parseUnixFloatAsDateTime(ts) this.log.debug(`parsed date ${ts} -> ${date}`) - let storageTable = await DbHelper.findStorageTableByDate(nsName, nsShardId, date); + let storageTable = await DbHelper.findStorageTableByDate(nsName, nsShardId, date) this.log.debug(`found table ${storageTable}`) if (StrUtil.isEmpty(storageTable)) { - this.log.error('storage table not found'); - let monthStart = date.startOf('month').toISODate().toString(); - let monthEndExclusive = date.startOf('month').plus({months: 1}).toISODate().toString(); - this.log.debug('creating new storage table'); - const dateYYYYMM = DateUtil.formatYYYYMM(date); - const tableName = `storage_ns_${nsName}_d_${dateYYYYMM}`; - const recordCreated = await DbHelper.createNewNodestorageRecord(nsName, nsShardId, - monthStart, monthEndExclusive, tableName); + this.log.error('storage table not found') + const monthStart = date.startOf('month').toISODate().toString() + const monthEndExclusive = date.startOf('month').plus({ months: 1 }).toISODate().toString() + this.log.debug('creating new storage table') + const dateYYYYMM = DateUtil.formatYYYYMM(date) + const tableName = `storage_ns_${nsName}_d_${dateYYYYMM}` + const recordCreated = await DbHelper.createNewNodestorageRecord( + nsName, + nsShardId, + monthStart, + monthEndExclusive, + tableName + ) if (recordCreated) { this.log.debug('record created: ', recordCreated) // we've added a new record to node_storage_layout => we can safely try to create a table // otherwise, if many connections attempt to create a table from multiple threads // it leads to postgres deadlock sometimes - await DbHelper.createNewStorageTable(tableName); + await DbHelper.createNewStorageTable(tableName) this.log.debug('creating node storage layout mapping') - storageTable = tableName; + storageTable = tableName } } - const storageValue = await DbHelper.putValueInTable(nsName, nsShardId, nsId, storageTable, - ts, key, JSON.stringify(fpayload)); + const storageValue = await DbHelper.putValueInTable( + nsName, + nsShardId, + nsId, + storageTable, + ts, + key, + JSON.stringify(fpayload) + ) this.log.debug(`found value: ${storageValue}`) } // todo remove shard entries from node_storage_layout also? public async deleteShardsFromInboxes(shardsToDelete: Set) { - this.log.debug('deleteShardsFromInboxes(): shardsToDelete: %j', - Coll.setToArray(shardsToDelete)); + this.log.debug('deleteShardsFromInboxes(): shardsToDelete: %j', Coll.setToArray(shardsToDelete)) if (shardsToDelete.size == 0) { - return; + return } // delete from index - let idsToDelete = Coll.numberSetToSqlQuoted(shardsToDelete); + const idsToDelete = Coll.numberSetToSqlQuoted(shardsToDelete) const rows = await PgUtil.queryArr<{ table_name: string }>( `select distinct table_name from node_storage_layout - where namespace_shard_id in ${idsToDelete}`); + where namespace_shard_id in ${idsToDelete}` + ) for (const row of rows) { - this.log.debug('clearing table %s from shards %o', row, idsToDelete); - await PgUtil.update(`delete + this.log.debug('clearing table %s from shards %o', row, idsToDelete) + await PgUtil.update( + `delete from ${row.table_name} where namespace_shard_id in ${idsToDelete}`, - idsToDelete); + idsToDelete + ) } } - -} \ No newline at end of file +} diff --git a/src/services/messaging/queueManager.ts b/src/services/messaging/queueManager.ts index 58c8586..3fdac61 100644 --- a/src/services/messaging/queueManager.ts +++ b/src/services/messaging/queueManager.ts @@ -1,27 +1,29 @@ -import {Inject, Service} from 'typedi' -import {Logger} from 'winston' import schedule from 'node-schedule' -import {ValidatorContractState} from '../messaging-common/validatorContractState' -import {WinstonUtil} from '../../utilz/winstonUtil' -import {QueueServer} from '../messaging-dset/queueServer' -import {QueueClient} from '../messaging-dset/queueClient' -import StorageNode from "./storageNode"; -import {QueueClientHelper} from "../messaging-common/queueClientHelper"; -import {EnvLoader} from "../../utilz/envLoader"; +import { Inject, Service } from 'typedi' +import { Logger } from 'winston' +import { EnvLoader } from '../../utilz/envLoader' +import { WinstonUtil } from '../../utilz/winstonUtil' +import { QueueClientHelper } from '../messaging-common/queueClientHelper' +import { ValidatorContractState } from '../messaging-common/validatorContractState' +import { QueueClient } from '../messaging-dset/queueClient' +import { QueueServer } from '../messaging-dset/queueServer' +import StorageNode from './storageNode' @Service() export class QueueManager { public log: Logger = WinstonUtil.newLog(QueueManager) @Inject((type) => ValidatorContractState) - private contract: ValidatorContractState; - @Inject(type => StorageNode) - private storageNode:StorageNode; - + private contract: ValidatorContractState + @Inject((type) => StorageNode) + private storageNode: StorageNode // PING: schedule - private readonly CLIENT_READ_SCHEDULE = EnvLoader.getPropertyOrDefault('CLIENT_READ_SCHEDULE', '*/30 * * * * *'); + private readonly CLIENT_READ_SCHEDULE = EnvLoader.getPropertyOrDefault( + 'CLIENT_READ_SCHEDULE', + '*/30 * * * * *' + ) public static QUEUE_MBLOCK = 'mblock' mblockQueue: QueueServer @@ -36,17 +38,19 @@ export class QueueManager { public async postConstruct() { this.log.debug('postConstruct') this.mblockClient = new QueueClient(this.storageNode, QueueManager.QUEUE_MBLOCK) - await QueueClientHelper.initClientForEveryQueueForEveryValidator(this.contract, [QueueManager.QUEUE_MBLOCK]) + await QueueClientHelper.initClientForEveryQueueForEveryValidator(this.contract, [ + QueueManager.QUEUE_MBLOCK + ]) const qs = this schedule.scheduleJob(this.CLIENT_READ_SCHEDULE, async function () { const dbgPrefix = 'PollRemoteQueue' try { await qs.mblockClient.pollRemoteQueue(qs.CLIENT_REQUEST_PER_SCHEDULED_JOB) - qs.log.info(`CRON %s started`, dbgPrefix); + qs.log.info(`CRON %s started`, dbgPrefix) } catch (err) { - qs.log.error(`CRON %s failed %o`, dbgPrefix, err); + qs.log.error(`CRON %s failed %o`, dbgPrefix, err) } finally { - qs.log.info(`CRON %s finished`, dbgPrefix); + qs.log.info(`CRON %s finished`, dbgPrefix) } }) } @@ -63,9 +67,9 @@ export class QueueManager { return { result: lastOffset } } - public async readItems(dsetName: string, firstOffset: number){ + public async readItems(dsetName: string, firstOffset: number) { const q = this.getQueue(dsetName) - return await q.readWithLastOffset(firstOffset); + return await q.readWithLastOffset(firstOffset) } public async pollRemoteQueues(): Promise { @@ -73,5 +77,3 @@ export class QueueManager { return result } } - - diff --git a/src/services/messaging/storageNode.ts b/src/services/messaging/storageNode.ts index 0f69292..d965563 100644 --- a/src/services/messaging/storageNode.ts +++ b/src/services/messaging/storageNode.ts @@ -1,16 +1,20 @@ -import {Inject, Service} from 'typedi' -import {ValidatorContractState} from "../messaging-common/validatorContractState"; -import {Logger} from "winston"; -import {FPayload, MessageBlock, MessageBlockUtil} from "../messaging-common/messageBlock"; -import {QueueClient} from "../messaging-dset/queueClient"; -import {Consumer, QItem} from "../messaging-dset/queueTypes"; -import {BlockStorage} from "./BlockStorage"; -import {QueueManager} from "./queueManager"; -import {Coll} from "../../utilz/coll"; -import {Check} from "../../utilz/check"; -import {WinstonUtil} from "../../utilz/winstonUtil"; -import {StorageContractListener, StorageContractState} from "../messaging-common/storageContractState"; -import {IndexStorage} from "./IndexStorage"; +import { Inject, Service } from 'typedi' +import { Logger } from 'winston' + +import { Check } from '../../utilz/check' +import { Coll } from '../../utilz/coll' +import { WinstonUtil } from '../../utilz/winstonUtil' +import { MessageBlock, MessageBlockUtil } from '../messaging-common/messageBlock' +import { + StorageContractListener, + StorageContractState +} from '../messaging-common/storageContractState' +import { ValidatorContractState } from '../messaging-common/validatorContractState' +import { QueueClient } from '../messaging-dset/queueClient' +import { Consumer, QItem } from '../messaging-dset/queueTypes' +import { BlockStorage } from './BlockStorage' +import { IndexStorage } from './IndexStorage' +import { QueueManager } from './queueManager' // todo reshard(): // raise a flag while executing this; handle new updates somehow? @@ -22,98 +26,121 @@ export default class StorageNode implements Consumer, StorageContractList public log: Logger = WinstonUtil.newLog(StorageNode) @Inject() - private valContractState: ValidatorContractState; + private valContractState: ValidatorContractState @Inject() - private storageContractState: StorageContractState; + private storageContractState: StorageContractState - @Inject(type => QueueManager) - private queueManager: QueueManager; + @Inject((type) => QueueManager) + private queueManager: QueueManager @Inject() - private blockStorage: BlockStorage; + private blockStorage: BlockStorage @Inject() - private indexStorage: IndexStorage; + private indexStorage: IndexStorage - private client: QueueClient; + private client: QueueClient public async postConstruct() { - await this.blockStorage.postConstruct(); - await this.indexStorage.postConstruct(); - await this.valContractState.postConstruct(); - await this.storageContractState.postConstruct(true, this); - await this.queueManager.postConstruct(); + await this.blockStorage.postConstruct() + await this.indexStorage.postConstruct() + await this.valContractState.postConstruct() + await this.storageContractState.postConstruct(true, this) + await this.queueManager.postConstruct() } // remote queue handler async accept(item: QItem): Promise { // check hash - let mb = item.object; - Check.notEmpty(mb.id, 'message block has no id'); - let calculatedHash = MessageBlockUtil.calculateHash(mb); + const mb = item.object + Check.notEmpty(mb.id, 'message block has no id') + const calculatedHash = MessageBlockUtil.calculateHash(mb) if (calculatedHash !== item.object_hash) { - this.log.error('received item hash=%s , ' + - 'which differs from calculatedHash=%s, ' + - 'ignoring the block because producer calculated the hash incorrectly', - item.object_hash, calculatedHash); - return false; + this.log.error( + 'received item hash=%s , ' + + 'which differs from calculatedHash=%s, ' + + 'ignoring the block because producer calculated the hash incorrectly', + item.object_hash, + calculatedHash + ) + return false } // check contents // since this check is not for historical data, but for realtime data, // so we do not care about old blocked validators which might occur in the historical queue - let activeValidators = Coll.arrayToFields(this.valContractState.getActiveValidators(), 'nodeId'); - let check1 = MessageBlockUtil.checkBlock(mb, activeValidators); + const activeValidators = Coll.arrayToFields( + this.valContractState.getActiveValidators(), + 'nodeId' + ) + const check1 = MessageBlockUtil.checkBlock(mb, activeValidators) if (!check1.success) { - this.log.error('item validation failed: ', check1.err); - return false; + this.log.error('item validation failed: ', check1.err) + return false } // check database - let shardSet = MessageBlockUtil.calculateAffectedShards(mb, this.storageContractState.shardCount); - let isNew = await this.blockStorage.saveBlockWithShardData(mb, calculatedHash, shardSet); + const shardSet = MessageBlockUtil.calculateAffectedShards( + mb, + this.storageContractState.shardCount + ) + const isNew = await this.blockStorage.saveBlockWithShardData(mb, calculatedHash, shardSet) if (!isNew) { // this is not an error, because we read duplicates from every validator - this.log.debug('block %s already exists ', mb.id); - return false; + this.log.debug('block %s already exists ', mb.id) + return false } // send block - await this.indexStorage.unpackBlockToInboxes(mb, shardSet); + await this.indexStorage.unpackBlockToInboxes(mb, shardSet) } - - public async handleReshard(currentNodeShards: Set|null, allNodeShards: Map>) { - let newShards = currentNodeShards ?? new Set(); - const oldShards = await this.blockStorage.loadNodeShards(); - this.log.debug('handleReshard(): newShards: %j oldShards: %j', - Coll.setToArray(newShards), Coll.setToArray(oldShards)) + public async handleReshard( + currentNodeShards: Set | null, + allNodeShards: Map> + ) { + const newShards = currentNodeShards ?? new Set() + const oldShards = await this.blockStorage.loadNodeShards() + this.log.debug( + 'handleReshard(): newShards: %j oldShards: %j', + Coll.setToArray(newShards), + Coll.setToArray(oldShards) + ) if (Coll.isEqualSet(newShards, oldShards)) { this.log.debug('handleReshard(): no reshard is needed') - return; + return } - let shardsToAdd = Coll.substractSet(newShards, oldShards); - let shardsToDelete = Coll.substractSet(oldShards, newShards); - let commonShards = Coll.intersectSet(oldShards, newShards); - this.log.debug('shardsToAdd %j shardsToDelete %j shardsRemaining %j', + const shardsToAdd = Coll.substractSet(newShards, oldShards) + const shardsToDelete = Coll.substractSet(oldShards, newShards) + const commonShards = Coll.intersectSet(oldShards, newShards) + this.log.debug( + 'shardsToAdd %j shardsToDelete %j shardsRemaining %j', Coll.setToArray(shardsToAdd), Coll.setToArray(shardsToDelete), - Coll.setToArray(commonShards)); + Coll.setToArray(commonShards) + ) - await this.indexStorage.deleteShardsFromInboxes(shardsToDelete); + await this.indexStorage.deleteShardsFromInboxes(shardsToDelete) // add to index // reprocess every block from blocks table (only once per each block) // if the block has shardsToAdd -> add anything which is in shardsToAdd - const pageSize = 30; - await this.blockStorage.iterateAllStoredBlocks(this.storageContractState.shardCount, + const pageSize = 30 + await this.blockStorage.iterateAllStoredBlocks( + this.storageContractState.shardCount, pageSize, shardsToAdd, async (messageBlockJson, messageBlockHash, messageBlockShards) => { - let mb: MessageBlock = JSON.parse(messageBlockJson); - let shardsToAddFromBlock = Coll.intersectSet(shardsToAdd, messageBlockShards); - this.log.debug('reindexing block %s, blockShards %s, shardsToAdd %s,, shardsToAddFromBlock', - messageBlockHash, Coll.setToArray(messageBlockShards), Coll.setToArray(shardsToAdd), Coll.setToArray(shardsToAddFromBlock)) - await this.indexStorage.unpackBlockToInboxes(mb, shardsToAddFromBlock); - }); - await this.blockStorage.saveNodeShards(newShards); + const mb: MessageBlock = JSON.parse(messageBlockJson) + const shardsToAddFromBlock = Coll.intersectSet(shardsToAdd, messageBlockShards) + this.log.debug( + 'reindexing block %s, blockShards %s, shardsToAdd %s,, shardsToAddFromBlock', + messageBlockHash, + Coll.setToArray(messageBlockShards), + Coll.setToArray(shardsToAdd), + Coll.setToArray(shardsToAddFromBlock) + ) + await this.indexStorage.unpackBlockToInboxes(mb, shardsToAddFromBlock) + } + ) + await this.blockStorage.saveNodeShards(newShards) } -} \ No newline at end of file +} diff --git a/src/utilz/bitUtil.ts b/src/utilz/bitUtil.ts index 22c8115..d7a9e1f 100644 --- a/src/utilz/bitUtil.ts +++ b/src/utilz/bitUtil.ts @@ -1,4 +1,4 @@ -import {Coll} from "./coll"; +import { Coll } from './coll' export class BitUtil { /** @@ -24,8 +24,8 @@ export class BitUtil { target = new Buffer(add.length) src.copy(target, 0, 0, src.length) } - var length = Math.min(target.length, add.length) - for (var i = 0; i < length; ++i) { + const length = Math.min(target.length, add.length) + for (let i = 0; i < length; ++i) { target[i] = target[i] ^ add[i] } return target @@ -39,22 +39,22 @@ export class BitUtil { return Buffer.from(value, 'base64').toString('utf8') } - public static getBit(number:number, bitOffset:number) { - return (number & (1 << bitOffset)) === 0 ? 0 : 1; + public static getBit(number: number, bitOffset: number) { + return (number & (1 << bitOffset)) === 0 ? 0 : 1 } public static bitsToPositions(number: number): number[] { // return null; - const result: number[] = []; - let position = 0; + const result: number[] = [] + let position = 0 while (number !== 0) { if ((number & 1) === 1) { - result.push(position); + result.push(position) } - number = number >>> 1; - position++; + number = number >>> 1 + position++ } - Coll.sortNumbersAsc(result); - return result; + Coll.sortNumbersAsc(result) + return result } } diff --git a/src/utilz/coll.ts b/src/utilz/coll.ts index b700d2d..4528ff8 100644 --- a/src/utilz/coll.ts +++ b/src/utilz/coll.ts @@ -1,7 +1,6 @@ // CollectionUtils // all the proper type safe way to work with JS collections/sets/arrays export class Coll { - public static arrayToMap(arr: V[], keyField: K): Map { if (arr == null || arr.length == 0) { return new Map() @@ -13,20 +12,19 @@ export class Coll { if (map == null || map.size == 0) { return [] } - return [...map.values()]; + return [...map.values()] } public static mapKeysToArray(map: Map): K[] { if (map == null || map.size == 0) { return [] } - return [...map.keys()]; + return [...map.keys()] } - public static arrayToSet(arr: V[]): Set { if (arr == null) { - return new Set(); + return new Set() } return new Set(arr) } @@ -45,59 +43,65 @@ export class Coll { public static setToArray(set: Set): V[] { if (set == null) { - return []; + return [] } return Array.from(set.keys()) } // [1,2,3] - [2,3] = [1] public static substractSet(set1: Set, set2: Set): Set { - return new Set([...set1].filter(x => !set2.has(x))); + return new Set([...set1].filter((x) => !set2.has(x))) } // [1,2,3] x [2, 3] = [2,3] public static intersectSet(set1: Set, set2: Set): Set { - return new Set([...set1].filter(x => set2.has(x))); + return new Set([...set1].filter((x) => set2.has(x))) } // [1,2,3] x [2, 3] = [2,3] public static addSet(set1: Set, set2: Set): Set { - return new Set([...set1, ...set2]); + return new Set([...set1, ...set2]) } public static sortNumbersAsc(array: number[]) { if (array == null || array.length == 0) { - return; + return } array.sort((a, b) => { - return a - b; + return a - b }) } public static isEqualSet(a: Set, b: Set) { - if (a === b) return true; - if (a.size !== b.size) return false; + if (a === b) return true + if (a.size !== b.size) return false for (const value of a) { if (!b.has(value)) { - return false; + return false } } - return true; + return true } // parse '[1,2,3]' into Set: 1,2,3 public static parseAsNumberSet(jsonArray: string): Set { - const arr: number[] = JSON.parse(jsonArray); - return Coll.arrayToSet(arr); + const arr: number[] = JSON.parse(jsonArray) + return Coll.arrayToSet(arr) } // store set 1,2,3 as array: [1,2,3] public static numberSetToJson(s: Set): string { - return JSON.stringify([...s]); + return JSON.stringify([...s]) } // set 1,2,3 to sql: ('1','2','3') public static numberSetToSqlQuoted(s: Set): string { - return '(' + Coll.setToArray(s).map(num => "'" + num + "'").join(',') + ')'; + return ( + '(' + + Coll.setToArray(s) + .map((num) => "'" + num + "'") + .join(',') + + ')' + ) } } diff --git a/src/utilz/dateUtil.ts b/src/utilz/dateUtil.ts index 4840b49..a20646d 100644 --- a/src/utilz/dateUtil.ts +++ b/src/utilz/dateUtil.ts @@ -43,7 +43,7 @@ export default class DateUtil { public static currentTimeSeconds(): number { // new Date().getTime() - return Math.round( Date.now()/ 1000) + return Math.round(Date.now() / 1000) } public static millisToDate(timestamp: number): Date { diff --git a/src/utilz/envLoader.ts b/src/utilz/envLoader.ts index 939fa82..9250fff 100644 --- a/src/utilz/envLoader.ts +++ b/src/utilz/envLoader.ts @@ -1,17 +1,17 @@ import dotenv from 'dotenv' + import StrUtil from './strUtil' export class EnvLoader { - public static loadEnvOrFail() { // loads all .env variables into process.env.* variables // Optional support for CONFIG_DIR variable - console.log(`config dir is ${process.env.CONFIG_DIR}`); - let options = {}; + console.log(`config dir is ${process.env.CONFIG_DIR}`) + let options = {} if (process.env.CONFIG_DIR) { - options = {path: `${process.env.CONFIG_DIR}/.env`}; + options = { path: `${process.env.CONFIG_DIR}/.env` } } - const envFound = dotenv.config(options); + const envFound = dotenv.config(options) if (envFound.error) { throw new Error("⚠️ Couldn't find .env file ⚠️") } @@ -31,10 +31,10 @@ export class EnvLoader { return val != null && val.toLowerCase() === 'true' } - public static getPropertyOrDefault(propName: string, def:string): string { + public static getPropertyOrDefault(propName: string, def: string): string { const val = process.env[propName] if (StrUtil.isEmpty(val)) { - return def; + return def } return val } diff --git a/src/utilz/ethSig.ts b/src/utilz/ethSig.ts index be11cd9..e1b06aa 100644 --- a/src/utilz/ethSig.ts +++ b/src/utilz/ethSig.ts @@ -1,6 +1,7 @@ -import {Wallet} from "ethers"; -import {verifyMessage} from "ethers/lib/utils"; -import {ObjectHasher} from "./objectHasher"; +import { Wallet } from 'ethers' +import { verifyMessage } from 'ethers/lib/utils' + +import { ObjectHasher } from './objectHasher' /** * Utitily class that allows @@ -10,26 +11,24 @@ import {ObjectHasher} from "./objectHasher"; * Ignores 'signature' properties */ export class EthSig { + public static async create(wallet: Wallet, ...object: any[]): Promise { + const ethMessage = ObjectHasher.hashToSha256IgnoreSig(object) + const sig = await wallet.signMessage(ethMessage) + return sig + } - public static async create(wallet: Wallet, ...object: any[]): Promise { - const ethMessage = ObjectHasher.hashToSha256IgnoreSig(object); - const sig = await wallet.signMessage(ethMessage); - return sig; - } - - public static check(sig: string, targetWallet: string, ...object: any[]): boolean { - const ethMessage = ObjectHasher.hashToSha256IgnoreSig(object); - const verificationAddress = verifyMessage(ethMessage, sig); - if (targetWallet !== verificationAddress) { - return false; - } - return true; + public static check(sig: string, targetWallet: string, ...object: any[]): boolean { + const ethMessage = ObjectHasher.hashToSha256IgnoreSig(object) + const verificationAddress = verifyMessage(ethMessage, sig) + if (targetWallet !== verificationAddress) { + return false } + return true + } - public static isEthZero(addr: string) { - return '0x0000000000000000000000000000000000000000' === addr - } + public static isEthZero(addr: string) { + return '0x0000000000000000000000000000000000000000' === addr + } } -export function Signed(target: Function) { -} \ No newline at end of file +export function Signed(target: Function) {} diff --git a/src/utilz/ethersUtil.ts b/src/utilz/ethersUtil.ts index 6f0613e..96cfd14 100644 --- a/src/utilz/ethersUtil.ts +++ b/src/utilz/ethersUtil.ts @@ -1,61 +1,68 @@ -import path from "path"; -import fs from "fs"; -import {EnvLoader} from "./envLoader"; -import {Contract, ethers, Wallet} from "ethers"; -import {JsonRpcProvider} from "@ethersproject/providers/src.ts/json-rpc-provider"; -import {Logger} from "winston"; -import {ValidatorCtClient} from "../services/messaging-common/validatorContractState"; -import {WinstonUtil} from "./winstonUtil"; +import { JsonRpcProvider } from '@ethersproject/providers/src.ts/json-rpc-provider' +import { Contract, ethers, Wallet } from 'ethers' +import fs from 'fs' +import path from 'path' + +import { WinstonUtil } from './winstonUtil' export class EthersUtil { - static log = WinstonUtil.newLog(EthersUtil); + static log = WinstonUtil.newLog(EthersUtil) public static loadAbi(configDir: string, fileNameInConfigDir: string): string { - const fileAbsolute = path.resolve(configDir, `./${fileNameInConfigDir}`); - const file = fs.readFileSync(fileAbsolute, 'utf8'); - const json = JSON.parse(file); - const abi = json.abi; - console.log(`abi size:`, abi.length); - return abi; + const fileAbsolute = path.resolve(configDir, `./${fileNameInConfigDir}`) + const file = fs.readFileSync(fileAbsolute, 'utf8') + const json = JSON.parse(file) + const abi = json.abi + console.log(`abi size:`, abi.length) + return abi } // creates a client, using an encrypted private key from disk, so that we could sign/write to the blockchain - public static async connectWithKey(configDir: string, - privateKeyFileName: string, - privateKeyPass: string, - contractAbiFileName: string, - contractAddr: string, - provider: JsonRpcProvider): Promise { - let abi = EthersUtil.loadAbi(configDir, contractAbiFileName); - const jsonFile = fs.readFileSync(configDir + '/' + privateKeyFileName, 'utf-8'); - let nodeWallet = await Wallet.fromEncryptedJson(jsonFile, privateKeyPass); - let nodeAddress = await nodeWallet.getAddress(); + public static async connectWithKey( + configDir: string, + privateKeyFileName: string, + privateKeyPass: string, + contractAbiFileName: string, + contractAddr: string, + provider: JsonRpcProvider + ): Promise { + const abi = EthersUtil.loadAbi(configDir, contractAbiFileName) + const jsonFile = fs.readFileSync(configDir + '/' + privateKeyFileName, 'utf-8') + const nodeWallet = await Wallet.fromEncryptedJson(jsonFile, privateKeyPass) + const nodeAddress = await nodeWallet.getAddress() const signer = nodeWallet.connect(provider) - const contract = new ethers.Contract(contractAddr, abi, signer); - this.log.debug('connecting contract %s using signer %s (keydir: %s, keyfile: %s, abi: %s) ', - contractAddr, signer.address, configDir, privateKeyFileName, contractAbiFileName); + const contract = new ethers.Contract(contractAddr, abi, signer) + this.log.debug( + 'connecting contract %s using signer %s (keydir: %s, keyfile: %s, abi: %s) ', + contractAddr, + signer.address, + configDir, + privateKeyFileName, + contractAbiFileName + ) return { contract, nodeWallet, nodeAddress - }; + } } // creates a client which can only read blockchain state - public static async connectWithoutKey(configDir: string, - contractAbiFileName: string, - contractAddr: string, - provider: JsonRpcProvider): Promise { - let abi = EthersUtil.loadAbi(configDir, contractAbiFileName); - const contract = new ethers.Contract(contractAddr, abi, provider); - this.log.debug('connecting contract %s (no key, abi: %s) ', - contractAddr, contractAbiFileName); - return contract; + public static async connectWithoutKey( + configDir: string, + contractAbiFileName: string, + contractAddr: string, + provider: JsonRpcProvider + ): Promise { + const abi = EthersUtil.loadAbi(configDir, contractAbiFileName) + const contract = new ethers.Contract(contractAddr, abi, provider) + this.log.debug('connecting contract %s (no key, abi: %s) ', contractAddr, contractAbiFileName) + return contract } } type ContractWithMeta = { - contract: Contract; - nodeWallet: Wallet; - nodeAddress: string; -} \ No newline at end of file + contract: Contract + nodeWallet: Wallet + nodeAddress: string +} diff --git a/src/utilz/expressUtil.ts b/src/utilz/expressUtil.ts index dd8e671..31badaa 100644 --- a/src/utilz/expressUtil.ts +++ b/src/utilz/expressUtil.ts @@ -1,4 +1,5 @@ import { NextFunction, Request, Response } from 'express' + import { WinstonUtil } from './winstonUtil' export class ExpressUtil { diff --git a/src/utilz/idUtil.ts b/src/utilz/idUtil.ts index 17f32cb..4d742f1 100755 --- a/src/utilz/idUtil.ts +++ b/src/utilz/idUtil.ts @@ -1,8 +1,7 @@ -import * as uuid from "uuid"; +import * as uuid from 'uuid' export default class IdUtil { - - public static getUuidV4(): string { - return uuid.v4(); - } -} \ No newline at end of file + public static getUuidV4(): string { + return uuid.v4() + } +} diff --git a/src/utilz/mySqlUtil.ts b/src/utilz/mySqlUtil.ts index 30621b2..bb9c7b5 100644 --- a/src/utilz/mySqlUtil.ts +++ b/src/utilz/mySqlUtil.ts @@ -1,7 +1,8 @@ -import { Logger } from 'winston' import { OkPacket, Pool } from 'mysql' -import { WinstonUtil } from './winstonUtil' +import { Logger } from 'winston' + import { EnvLoader } from './envLoader' +import { WinstonUtil } from './winstonUtil' /* A sync replacement of db.query callback-y code diff --git a/src/utilz/numUtil.ts b/src/utilz/numUtil.ts index 1331ad6..7961ca1 100644 --- a/src/utilz/numUtil.ts +++ b/src/utilz/numUtil.ts @@ -11,7 +11,7 @@ export class NumUtil { } static isRoundedInteger(valN: number) { - return Number.isInteger(valN); + return Number.isInteger(valN) } public static toString(value: number) { diff --git a/src/utilz/pgUtil.ts b/src/utilz/pgUtil.ts index eeab171..87fd217 100644 --- a/src/utilz/pgUtil.ts +++ b/src/utilz/pgUtil.ts @@ -1,10 +1,10 @@ -import {Logger} from 'winston' -import {OkPacket, Pool} from 'mysql' -import {WinstonUtil} from './winstonUtil' -import {EnvLoader} from './envLoader' -import StrUtil from "./strUtil"; -import pg from "pg-promise/typescript/pg-subset"; -import {IDatabase} from "pg-promise"; +import { IDatabase } from 'pg-promise' +import pg from 'pg-promise/typescript/pg-subset' +import { Logger } from 'winston' + +import { EnvLoader } from './envLoader' +import StrUtil from './strUtil' +import { WinstonUtil } from './winstonUtil' // PG PROMISE https://github.com/vitaly-t/pg-promise @@ -13,10 +13,10 @@ import {IDatabase} from "pg-promise"; export class PgUtil { private static log: Logger = WinstonUtil.newLog('pg') static logSql = false - static pool:IDatabase<{}, pg.IClient>; // todo unknown type ??? + static pool: IDatabase<{}, pg.IClient> // todo unknown type ??? - public static init(pool:IDatabase<{}, pg.IClient>) { - PgUtil.pool = pool; + public static init(pool: IDatabase<{}, pg.IClient>) { + PgUtil.pool = pool if (!PgUtil.logSql && EnvLoader.getPropertyAsBool('LOG_SQL_STATEMENTS')) { // todo add logging query + values PgUtil.logSql = true @@ -61,26 +61,25 @@ export class PgUtil { } public static async update(query: string, ...sqlArgs: any[]): Promise { - query = StrUtil.replaceAllMySqlToPostre(query); - this.log.debug(query, ' ---> args ', sqlArgs); - let result = await this.pool.result(query, sqlArgs,r => r.rowCount); - return result; + query = StrUtil.replaceAllMySqlToPostre(query) + this.log.debug(query, ' ---> args ', sqlArgs) + const result = await this.pool.result(query, sqlArgs, (r) => r.rowCount) + return result } public static async insert(query: string, ...sqlArgs: any[]): Promise { - query = StrUtil.replaceAllMySqlToPostre(query); - this.log.debug(query, ' ---> args ', sqlArgs); - let result = await this.pool.result(query, sqlArgs,r => r.rowCount); - return result; + query = StrUtil.replaceAllMySqlToPostre(query) + this.log.debug(query, ' ---> args ', sqlArgs) + const result = await this.pool.result(query, sqlArgs, (r) => r.rowCount) + return result } public static async queryArr(query: string, ...sqlArgs: any[]): Promise { - query = StrUtil.replaceAllMySqlToPostre(query); - this.log.debug(query, ' ---> args ', sqlArgs); - let result = await this.pool.query(query, sqlArgs); - return result; + query = StrUtil.replaceAllMySqlToPostre(query) + this.log.debug(query, ' ---> args ', sqlArgs) + const result = await this.pool.query(query, sqlArgs) + return result } - } /* diff --git a/src/utilz/promiseUtil.ts b/src/utilz/promiseUtil.ts index a133a64..855bdb1 100644 --- a/src/utilz/promiseUtil.ts +++ b/src/utilz/promiseUtil.ts @@ -1,92 +1,90 @@ export class PromiseUtil { - - // Waits for all promises to complete - public static allSettled(promises: Promise[]): Promise[]> { - let wrappedPromises = promises.map(p => { - return Promise.resolve(p) - .then( - val => new PromiseResult(PromiseResultType.SUCCESS, val, null), - err => new PromiseResult(PromiseResultType.FAILED, null, err)); - }); - return Promise.all(wrappedPromises); - } - - public static async sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + // Waits for all promises to complete + public static allSettled(promises: Promise[]): Promise[]> { + const wrappedPromises = promises.map((p) => { + return Promise.resolve(p).then( + (val) => new PromiseResult(PromiseResultType.SUCCESS, val, null), + (err) => new PromiseResult(PromiseResultType.FAILED, null, err) + ) + }) + return Promise.all(wrappedPromises) + } + + public static async sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) + } + + public static createDeferred( + rejectTimeout: number = 0, + resolveTimeout: number = 0 + ): DeferredPromise { + const deferred = new DeferredPromise() + deferred.promise = new Promise((resolve, reject) => { + deferred.resolve = resolve + deferred.reject = reject + }) + if (rejectTimeout > 0) { + setTimeout(function () { + deferred.reject() + }, rejectTimeout) } - - public static createDeferred(rejectTimeout: number = 0, - resolveTimeout: number = 0): DeferredPromise { - let deferred = new DeferredPromise(); - deferred.promise = new Promise((resolve, reject) => { - deferred.resolve = resolve; - deferred.reject = reject; - }); - if (rejectTimeout > 0) { - setTimeout(function () { - deferred.reject(); - }, rejectTimeout); - } - if (resolveTimeout > 0) { - setTimeout(function () { - deferred.resolve(); - }, resolveTimeout); - } - return deferred; + if (resolveTimeout > 0) { + setTimeout(function () { + deferred.resolve() + }, resolveTimeout) } - - + return deferred + } } export enum PromiseResultType { - FAILED = -1, - RUNNING = 0, - SUCCESS = 1 + FAILED = -1, + RUNNING = 0, + SUCCESS = 1 } export class PromiseResult { - private _status: PromiseResultType = PromiseResultType.RUNNING; - private _val: T; - private _err: any; - - constructor(status: number, val: T, err: any) { - this._status = status; - this._val = val; - this._err = err; - } - - public isFullfilled(): boolean { - return this._status == PromiseResultType.SUCCESS; - } - - public isSuccess(): boolean { - return this._status == PromiseResultType.SUCCESS; - } - - public isRejected(): boolean { - return this._status == PromiseResultType.FAILED; - } - - public isRunning(): boolean { - return this._status == PromiseResultType.RUNNING; - } - - get status(): PromiseResultType { - return this._status; - } - - get val(): T { - return this._val; - } - - get err(): any { - return this._err; - } + private _status: PromiseResultType = PromiseResultType.RUNNING + private _val: T + private _err: any + + constructor(status: number, val: T, err: any) { + this._status = status + this._val = val + this._err = err + } + + public isFullfilled(): boolean { + return this._status == PromiseResultType.SUCCESS + } + + public isSuccess(): boolean { + return this._status == PromiseResultType.SUCCESS + } + + public isRejected(): boolean { + return this._status == PromiseResultType.FAILED + } + + public isRunning(): boolean { + return this._status == PromiseResultType.RUNNING + } + + get status(): PromiseResultType { + return this._status + } + + get val(): T { + return this._val + } + + get err(): any { + return this._err + } } export class DeferredPromise { - promise: Promise; - resolve: Function; - reject: Function; + promise: Promise + resolve: Function + reject: Function } - diff --git a/src/utilz/strUtil.ts b/src/utilz/strUtil.ts index b538954..814ca3e 100755 --- a/src/utilz/strUtil.ts +++ b/src/utilz/strUtil.ts @@ -1,22 +1,21 @@ export default class StrUtil { - public static isEmpty(s: string): boolean { if (s == null) { - return true; + return true } if (typeof s !== 'string') { - return false; + return false } return s.length === 0 } public static isHex(s: string): boolean { if (StrUtil.isEmpty(s)) { - return false; + return false } - let pattern = /^[A-F0-9]+$/i; - let result = pattern.test(s); - return result; + const pattern = /^[A-F0-9]+$/i + const result = pattern.test(s) + return result } /** @@ -25,22 +24,25 @@ export default class StrUtil { * @param defaultValue */ public static getOrDefault(s: string, defaultValue: string) { - return StrUtil.isEmpty(s) ? defaultValue : s; + return StrUtil.isEmpty(s) ? defaultValue : s } public static toStringDeep(obj: any): string { - return JSON.stringify(obj, null, 4); + return JSON.stringify(obj, null, 4) } // https://ethereum.stackexchange.com/questions/2045/is-ethereum-wallet-address-case-sensitive public static normalizeEthAddress(addr: string): string { - return addr; + return addr } - public static replaceAll(str: string, - find: string[], replace: string[], - regexFlags: string):string { - var gFlag = false + public static replaceAll( + str: string, + find: string[], + replace: string[], + regexFlags: string + ): string { + let gFlag = false if (typeof str !== 'string') { throw new TypeError('`str` parameter must be a string!') @@ -74,12 +76,12 @@ export default class StrUtil { regexFlags = 'g' } - var done = [] - var joined = find.join(')|(') - var regex = new RegExp('(' + joined + ')', regexFlags) + const done = [] + const joined = find.join(')|(') + const regex = new RegExp('(' + joined + ')', regexFlags) return str.replace(regex, (match, ...finds) => { - var replaced + let replaced finds.some((found, index) => { if (found !== undefined) { @@ -106,9 +108,9 @@ export default class StrUtil { * aaaa?bbbb?cccc? => aaaa$1bbbb$2cccc$3 */ public static replaceAllMySqlToPostre(s: string): string { - let cnt = 1; + let cnt = 1 return s.replace(/\?/g, function () { - return `$${cnt++}`; - }); + return `$${cnt++}` + }) } } diff --git a/src/utilz/tuple.ts b/src/utilz/tuple.ts index b0f12b8..be99886 100644 --- a/src/utilz/tuple.ts +++ b/src/utilz/tuple.ts @@ -1 +1 @@ -type Tuple = [A, undefined?] | [undefined, B]; \ No newline at end of file +type Tuple = [A, undefined?] | [undefined, B] diff --git a/src/utilz/waitNotify.ts b/src/utilz/waitNotify.ts index d652d53..1840fac 100644 --- a/src/utilz/waitNotify.ts +++ b/src/utilz/waitNotify.ts @@ -1,50 +1,50 @@ -import {EventEmitter} from "events"; +import { EventEmitter } from 'events' -import IdUtil from "./idUtil"; +import IdUtil from './idUtil' export class WaitNotify { - ee: EventEmitter; - waitMsgIDs = []; + ee: EventEmitter + waitMsgIDs = [] - constructor() { - this.ee = new EventEmitter(); - this.waitMsgIDs = []; - } + constructor() { + this.ee = new EventEmitter() + this.waitMsgIDs = [] + } - async wait(timeout:number = 0) { - return new Promise((resolve, reject) => { - const msgID = IdUtil.getUuidV4(); - this.waitMsgIDs.push(msgID); - let timeoutId; - this.ee.once(msgID, () => { - if (timeoutId) { - clearTimeout(timeoutId); - } - resolve(); - }); - if (timeout) { - timeoutId = setTimeout(() => { - const delIndex = this.waitMsgIDs.indexOf(msgID); - if (delIndex !== -1) { - this.waitMsgIDs.splice(delIndex, 1); - reject(new Error('wait timeout')); - } - }, timeout); - } - }); - } + async wait(timeout: number = 0) { + return new Promise((resolve, reject) => { + const msgID = IdUtil.getUuidV4() + this.waitMsgIDs.push(msgID) + let timeoutId + this.ee.once(msgID, () => { + if (timeoutId) { + clearTimeout(timeoutId) + } + resolve() + }) + if (timeout) { + timeoutId = setTimeout(() => { + const delIndex = this.waitMsgIDs.indexOf(msgID) + if (delIndex !== -1) { + this.waitMsgIDs.splice(delIndex, 1) + reject(new Error('wait timeout')) + } + }, timeout) + } + }) + } - notify() { - this.notifyAll(); - } + notify() { + this.notifyAll() + } - notifyAll() { - while (this.waitMsgIDs.length > 0) { - this.ee.emit(this.waitMsgIDs.shift()); - } + notifyAll() { + while (this.waitMsgIDs.length > 0) { + this.ee.emit(this.waitMsgIDs.shift()) } + } - notifyOne() { - this.ee.emit(this.waitMsgIDs.shift()); - } -} \ No newline at end of file + notifyOne() { + this.ee.emit(this.waitMsgIDs.shift()) + } +} diff --git a/src/utilz/winstonUtil.ts b/src/utilz/winstonUtil.ts index 3850205..7286f57 100644 --- a/src/utilz/winstonUtil.ts +++ b/src/utilz/winstonUtil.ts @@ -1,8 +1,9 @@ -import {Format, TransformableInfo} from 'logform' -import {DateTime} from 'ts-luxon' +import { Format, TransformableInfo } from 'logform' +import { DateTime } from 'ts-luxon' import winston from 'winston' + +import { EnvLoader } from './envLoader' import StrUtil from './strUtil' -import {EnvLoader} from './envLoader' /* Example usage: @@ -52,17 +53,16 @@ I 230811 174624 [MyClass] Got alias List (SendMessage) */ - export class WinstonUtil { private static readonly CLASS_NAME_LENGTH = 23 - private static readonly LOG_DIR = EnvLoader.getPropertyOrFail('LOG_DIR'); - private static readonly LOG_LEVEL = EnvLoader.getPropertyOrFail('LOG_LEVEL'); - private static loggerMap: Map = new Map(); + private static readonly LOG_DIR = EnvLoader.getPropertyOrFail('LOG_DIR') + private static readonly LOG_LEVEL = EnvLoader.getPropertyOrFail('LOG_LEVEL') + private static loggerMap: Map = new Map() // all console writes drop here public static consoleTransport = new winston.transports.Console({ format: WinstonUtil.createFormat2WhichRendersClassName() - }); + }) // add debug writes drop here @@ -93,7 +93,7 @@ export class WinstonUtil { { "message": "Checking Node Version", "level": "info", "timestamp": "230809 180338", className?: "myClass"} */ public static renderFormat2(info: TransformableInfo) { - const {timestamp, level, message, meta} = info + const { timestamp, level, message, meta } = info const levelFirstChar = level == null ? '' : level.toUpperCase()[0] const date = DateTime.now() const formattedDate = date.toFormat('yyMMdd HHmmss') @@ -115,7 +115,7 @@ export class WinstonUtil { winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - winston.format.errors({stack: true}), + winston.format.errors({ stack: true }), winston.format.splat(), winston.format.json(), winston.format.printf((info) => { @@ -127,7 +127,7 @@ export class WinstonUtil { static createFormat2WhichRendersClassName(): Format { return winston.format.combine( - winston.format.errors({stack: true}), + winston.format.errors({ stack: true }), winston.format.splat(), winston.format.printf((info) => { return WinstonUtil.renderFormat2(info) @@ -156,7 +156,7 @@ export class WinstonUtil { WinstonUtil.debugFileTransport, WinstonUtil.errorFileTransport ] - }); + }) WinstonUtil.loggerMap.set(loggerName, loggerObj) return loggerObj } diff --git a/tests/DbHelper.test.ts b/tests/DbHelper.test.ts index 81c26c4..03dd041 100755 --- a/tests/DbHelper.test.ts +++ b/tests/DbHelper.test.ts @@ -1,27 +1,30 @@ -import chai from 'chai' import 'mocha' + +import chai from 'chai' + import DbHelper from '../src/helpers/dbHelper' -import {Container} from "node-docker-api/lib/container"; -import {filter} from "lodash"; chai.should() -const crypto = require("crypto"); +const crypto = require('crypto') const expect = chai.expect describe('DbHelpers tests', function () { - it('test 8 bit for 1 key ', function () { - let shardId = DbHelper.calculateShardForNamespaceIndex("feeds", "store0001"); - expect(shardId).lessThanOrEqual(255); - expect(shardId).greaterThanOrEqual(0) - console.log(shardId); - }) + it('test 8 bit for 1 key ', function () { + const shardId = DbHelper.calculateShardForNamespaceIndex('feeds', 'store0001') + expect(shardId).lessThanOrEqual(255) + expect(shardId).greaterThanOrEqual(0) + console.log(shardId) + }) - it('test 8 bit for 10 random keys', function () { - for (let i = 0; i < 10; i++) { - let shardId = DbHelper.calculateShardForNamespaceIndex(crypto.randomBytes(20).toString('hex'), crypto.randomBytes(20).toString('hex')); - expect(shardId).lessThanOrEqual(255); - expect(shardId).greaterThanOrEqual(0) - console.log(shardId); - } - }) + it('test 8 bit for 10 random keys', function () { + for (let i = 0; i < 10; i++) { + const shardId = DbHelper.calculateShardForNamespaceIndex( + crypto.randomBytes(20).toString('hex'), + crypto.randomBytes(20).toString('hex') + ) + expect(shardId).lessThanOrEqual(255) + expect(shardId).greaterThanOrEqual(0) + console.log(shardId) + } + }) }) diff --git a/tests/RandomUtil.test.ts b/tests/RandomUtil.test.ts index 17e1cd1..b3d5249 100755 --- a/tests/RandomUtil.test.ts +++ b/tests/RandomUtil.test.ts @@ -1,26 +1,25 @@ -import chai from 'chai' import 'mocha' -import DbHelper from '../src/helpers/dbHelper' -import { RandomUtil } from "dstorage-common"; + +import chai from 'chai' +import { RandomUtil } from 'dstorage-common' chai.should() -const crypto = require("crypto"); +const crypto = require('crypto') const expect = chai.expect describe('RandomUtil tests', function () { - it('random-1', function () { - console.log(RandomUtil.getRandomSubArray([1,2,3,4,5], 1)); - console.log(RandomUtil.getRandomSubArray([1,2,3,4,5], 2)); - console.log(RandomUtil.getRandomSubArray([1,2,3,4,5], 3)); - console.log(RandomUtil.getRandomSubArray([1,2,3,4,5], 4)); - console.log(RandomUtil.getRandomSubArray([1,2,3,4,5], 5)); - let failed = false; - try { - console.log(RandomUtil.getRandomSubArray([1, 2, 3, 4, 5], 6)); - } catch (e) { - failed = true; - } - if(!failed) expect(failed).equals(true); - }) - + it('random-1', function () { + console.log(RandomUtil.getRandomSubArray([1, 2, 3, 4, 5], 1)) + console.log(RandomUtil.getRandomSubArray([1, 2, 3, 4, 5], 2)) + console.log(RandomUtil.getRandomSubArray([1, 2, 3, 4, 5], 3)) + console.log(RandomUtil.getRandomSubArray([1, 2, 3, 4, 5], 4)) + console.log(RandomUtil.getRandomSubArray([1, 2, 3, 4, 5], 5)) + let failed = false + try { + console.log(RandomUtil.getRandomSubArray([1, 2, 3, 4, 5], 6)) + } catch (e) { + failed = true + } + if (!failed) expect(failed).equals(true) + }) }) diff --git a/tests/SNodeIntegration.test.ts b/tests/SNodeIntegration.test.ts index 581a490..fc2b838 100755 --- a/tests/SNodeIntegration.test.ts +++ b/tests/SNodeIntegration.test.ts @@ -1,241 +1,287 @@ +import { AxiosResponse } from 'axios' import chai from 'chai' -import {RandomUtil, PromiseUtil, EnvLoader} from 'dstorage-common' -import pgPromise, {IDatabase} from 'pg-promise'; -import {AxiosResponse} from "axios"; +import { EnvLoader, PromiseUtil, RandomUtil } from 'dstorage-common' +import pgPromise, { IDatabase } from 'pg-promise' // import crypto from 'crypto' -const crypto = require("crypto"); -var _ = require('lodash'); +const crypto = require('crypto') +const _ = require('lodash') const expect = chai.expect -import assert from 'assert-ts'; -const axios = require('axios'); -import {DateTime} from "ts-luxon"; -import {DateUtil} from "dstorage-common"; -import DbHelper from "../src/helpers/dbHelper"; +const axios = require('axios') +import { DateUtil } from 'dstorage-common' +import { DateTime } from 'ts-luxon' +import DbHelper from '../src/helpers/dbHelper' -EnvLoader.loadEnvOrFail(); - +EnvLoader.loadEnvOrFail() class SNode1Constants { - // DATA GENERATION/POST CHECKS POINTS TO S1 DATABASE - static dbUri = `postgres://${EnvLoader.getPropertyOrFail('TEST_SNODE_DB_USER')}:${EnvLoader.getPropertyOrFail('TEST_SNODE_DB_PASS')}@${EnvLoader.getPropertyOrFail('TEST_SNODE_DB_HOST')}:5432/${EnvLoader.getPropertyOrFail('TEST_SNODE_DB_NAME')}`; - // API TESTS POINT TO S1 API - static apiUrl = EnvLoader.getPropertyOrFail('TEST_SNODE_API_URL'); - static namespace = 'feeds'; + // DATA GENERATION/POST CHECKS POINTS TO S1 DATABASE + static dbUri = `postgres://${EnvLoader.getPropertyOrFail('TEST_SNODE_DB_USER')}:${EnvLoader.getPropertyOrFail('TEST_SNODE_DB_PASS')}@${EnvLoader.getPropertyOrFail('TEST_SNODE_DB_HOST')}:5432/${EnvLoader.getPropertyOrFail('TEST_SNODE_DB_NAME')}` + // API TESTS POINT TO S1 API + static apiUrl = EnvLoader.getPropertyOrFail('TEST_SNODE_API_URL') + static namespace = 'feeds' } -console.log(SNode1Constants.dbUri); +console.log(SNode1Constants.dbUri) function patchPromiseWithState() { - Object.defineProperty(Promise.prototype, "state", { - get: function () { - const o = {}; - return Promise.race([this, o]).then( - v => v === o ? "pending" : "resolved", - () => "rejected"); - } - }); + Object.defineProperty(Promise.prototype, 'state', { + get: function () { + const o = {} + return Promise.race([this, o]).then( + (v) => (v === o ? 'pending' : 'resolved'), + () => 'rejected' + ) + } + }) } -patchPromiseWithState(); +patchPromiseWithState() async function cleanAllTablesAndInitNetworkStorageLayout(db: IDatabase) { - await db.result('TRUNCATE TABLE network_storage_layout'); - console.log("cleaning up table: network_storage_layout"); + await db.result('TRUNCATE TABLE network_storage_layout') + console.log('cleaning up table: network_storage_layout') - await db.result('TRUNCATE TABLE node_storage_layout'); - console.log("cleaning up table: node_storage_layout"); + await db.result('TRUNCATE TABLE node_storage_layout') + console.log('cleaning up table: node_storage_layout') - const resultT2 = await db.result(`select string_agg(table_name, ',') as sql + const resultT2 = await db.result(`select string_agg(table_name, ',') as sql from information_schema.tables - where table_name like 'storage_%';`); - console.log(resultT2); - if (resultT2.rowCount > 0 && resultT2.rows[0].sql != null) { - let truncateStorageTablesSql = resultT2.rows[0].sql; - console.log("cleaning up storage tables with query: ", truncateStorageTablesSql); - const resultT3 = await db.result('TRUNCATE TABLE ' + truncateStorageTablesSql); - console.log("cleaning up tables: ", resultT3.duration, "ms"); - const resultT4 = await db.result('DROP TABLE ' + truncateStorageTablesSql); - console.log("dropping tables: ", resultT4.duration, "ms"); - } - - const maxNodeId = 3; - const maxShardId = 255; - const namespaceArr = ['feeds']; - let sql = `INSERT INTO network_storage_layout + where table_name like 'storage_%';`) + console.log(resultT2) + if (resultT2.rowCount > 0 && resultT2.rows[0].sql != null) { + const truncateStorageTablesSql = resultT2.rows[0].sql + console.log('cleaning up storage tables with query: ', truncateStorageTablesSql) + const resultT3 = await db.result('TRUNCATE TABLE ' + truncateStorageTablesSql) + console.log('cleaning up tables: ', resultT3.duration, 'ms') + const resultT4 = await db.result('DROP TABLE ' + truncateStorageTablesSql) + console.log('dropping tables: ', resultT4.duration, 'ms') + } + + const maxNodeId = 3 + const maxShardId = 255 + const namespaceArr = ['feeds'] + let sql = `INSERT INTO network_storage_layout (namespace, namespace_shard_id, node_id) - VALUES `; - let first = true; - for (let namespace of namespaceArr) { - for (let shardId = 0; shardId <= maxShardId; shardId++) { - for (let nodeId = 0; nodeId <= maxNodeId; nodeId++) { - sql += (first ? '' : ',') + `('${namespace}', '${shardId}', '${nodeId}')`; - first = false; - } - } + VALUES ` + let first = true + for (const namespace of namespaceArr) { + for (let shardId = 0; shardId <= maxShardId; shardId++) { + for (let nodeId = 0; nodeId <= maxNodeId; nodeId++) { + sql += (first ? '' : ',') + `('${namespace}', '${shardId}', '${nodeId}')` + first = false + } } - console.log(sql); - const result2 = await db.result(sql); - console.log("insert new data, affected rows ", result2.rowCount); + } + console.log(sql) + const result2 = await db.result(sql) + console.log('insert new data, affected rows ', result2.rowCount) } async function selectWithParam(db: IDatabase, value: string) { - const result1 = await db.result('SELECT $1 as VALUE', [value]); - console.log(result1.rows[0].value); + const result1 = await db.result('SELECT $1 as VALUE', [value]) + console.log(result1.rows[0].value) } -async function doPut(baseUri: string, ns: string, nsIndex: string, ts: number, key: string, data: any): Promise { - let url = `${baseUri}/api/v1/kv/ns/${ns}/nsidx/${nsIndex}/ts/${ts}/key/${key}`; - console.log(`PUT ${url}`, data); - let resp = await axios.post(url, data, {timeout: 5000}); - console.log(resp.status); - return resp; +async function doPut( + baseUri: string, + ns: string, + nsIndex: string, + ts: number, + key: string, + data: any +): Promise { + const url = `${baseUri}/api/v1/kv/ns/${ns}/nsidx/${nsIndex}/ts/${ts}/key/${key}` + console.log(`PUT ${url}`, data) + const resp = await axios.post(url, data, { timeout: 5000 }) + console.log(resp.status) + return resp } -async function doGet(baseUri: string, ns: string, nsIndex: string, date: string, key: string): Promise { - let resp = await axios.get(`${baseUri}/api/v1/kv/ns/${ns}/nsidx/${nsIndex}/date/${date}/key/${key}`, {timeout: 3000}); - console.log(resp.status, resp.data); - return resp; +async function doGet( + baseUri: string, + ns: string, + nsIndex: string, + date: string, + key: string +): Promise { + const resp = await axios.get( + `${baseUri}/api/v1/kv/ns/${ns}/nsidx/${nsIndex}/date/${date}/key/${key}`, + { timeout: 3000 } + ) + console.log(resp.status, resp.data) + return resp } -async function doList(baseUri: string, ns: string, nsIndex: string, month: string, firstTs?: number): Promise { - let url = `${baseUri}/api/v1/kv/ns/${ns}/nsidx/${nsIndex}/month/${month}/list/`; - if (firstTs != null) { - url += `?firstTs=${firstTs}` - } - let resp = await axios.post(url, {timeout: 3000}); - console.log('LIST', url, resp.status, resp.data); - return resp; +async function doList( + baseUri: string, + ns: string, + nsIndex: string, + month: string, + firstTs?: number +): Promise { + let url = `${baseUri}/api/v1/kv/ns/${ns}/nsidx/${nsIndex}/month/${month}/list/` + if (firstTs != null) { + url += `?firstTs=${firstTs}` + } + const resp = await axios.post(url, { timeout: 3000 }) + console.log('LIST', url, resp.status, resp.data) + return resp } async function performOneTest(baseUri: string, ns: string, testCounter: number): Promise { - const startTs = Date.now(); - console.log("-->started test", testCounter); - let key = crypto.randomUUID(); - let dataToDb = { - name: 'john' + '1', - surname: crypto.randomBytes(10).toString('base64'), - id: RandomUtil.getRandomInt(0, 100000) - }; - - let nsIndex = RandomUtil.getRandomInt(0, 100000).toString(); - - let dateObj = RandomUtil.getRandomDate(DateTime.fromObject({year: 2022, month: 1, day: 1}), - DateTime.fromObject({year: 2022, month: 12, day: 31})) - if (!dateObj.isValid) { - console.log("INVALID DATE !!! ", dateObj); - return - } - let dateFormatted = dateObj.toFormat('yyyyMMdd'); - console.log('dateFormatted', dateFormatted); + const startTs = Date.now() + console.log('-->started test', testCounter) + const key = crypto.randomUUID() + const dataToDb = { + name: 'john' + '1', + surname: crypto.randomBytes(10).toString('base64'), + id: RandomUtil.getRandomInt(0, 100000) + } + + const nsIndex = RandomUtil.getRandomInt(0, 100000).toString() + + const dateObj = RandomUtil.getRandomDate( + DateTime.fromObject({ year: 2022, month: 1, day: 1 }), + DateTime.fromObject({ year: 2022, month: 12, day: 31 }) + ) + if (!dateObj.isValid) { + console.log('INVALID DATE !!! ', dateObj) + return + } + const dateFormatted = dateObj.toFormat('yyyyMMdd') + console.log('dateFormatted', dateFormatted) + + const putResult = await doPut( + baseUri, + ns, + nsIndex, + dateObj.toUnixInteger() + 0.123456, + key, + dataToDb + ) + if (putResult.status != 201) { + console.log('PUT ERROR!!! ', putResult.status) + return + } + const getResult = await doGet(baseUri, ns, nsIndex, dateFormatted, key) + if (getResult.status != 200) { + console.log('GET ERROR!!! ', getResult.status) + return + } + const dataFromDb = getResult.data.items[0].payload + const isEqual = _.isEqual(dataToDb, dataFromDb) + if (!isEqual) { + console.log(`isEqual = `, isEqual) + console.log('dataToDb', dataToDb, 'dataFromDb', dataFromDb) + } + console.log('-->finished test', testCounter, ' elapsed ', (Date.now() - startTs) / 1000.0) + expect(isEqual).equals(true) + return Promise.resolve(getResult.status) +} - let putResult = await doPut(baseUri, ns, nsIndex, dateObj.toUnixInteger() + 0.123456, key, dataToDb); - if (putResult.status != 201) { - console.log('PUT ERROR!!! ', putResult.status); - return; +describe('snode-full', function () { + const pg = pgPromise({}) + const snodeDb = pg(SNode1Constants.dbUri) + it('snode-init', async function () { + this.timeout(30000) + await cleanAllTablesAndInitNetworkStorageLayout(snodeDb) + }) + + it('snode-put-get', async function () { + this.timeout(30000) + + const promiseArr = [] + console.log('PARALLEL_THREADS=', process.env.PARALLEL_THREADS) + const parallelThreads = process.env.PARALLEL_THREADS || 50 + for (let i = 0; i < parallelThreads; i++) { + try { + promiseArr.push(performOneTest(SNode1Constants.apiUrl, SNode1Constants.namespace, i)) // TODO !!!!!!!!! + } catch (e) { + console.log('failed to submit thread#', i) + } } - let getResult = await doGet(baseUri, ns, nsIndex, dateFormatted, key); - if (getResult.status != 200) { - console.log('GET ERROR!!! ', getResult.status); - return; + const result = await PromiseUtil.allSettled(promiseArr) + console.log(result) + for (const p of promiseArr) { + try { + await p + } catch (e) { + console.log('failed to await promise', e) + } + console.log(p.state) } - let dataFromDb = getResult.data.items[0].payload; - let isEqual = _.isEqual(dataToDb, dataFromDb); - if (!isEqual) { - console.log(`isEqual = `, isEqual); - console.log('dataToDb', dataToDb, 'dataFromDb', dataFromDb); + }) + + it('snode-test-list', async function () { + const numOfRowsToGenerate = 37 + const seedDate = DateUtil.buildDateTime(2015, 1, 22) + // this is almost unique inbox, so it's empty + const nsIndex = '' + RandomUtil.getRandomInt(1000, 10000000000000000) + // Generate data within the same month, only days are random + for (let i = 0; i < numOfRowsToGenerate; i++) { + const key = crypto.randomUUID() + const dataToDb = { + name: 'john' + '1', + surname: crypto.randomBytes(10).toString('base64'), + id: RandomUtil.getRandomInt(0, 100000) + } + const randomDayWithinSameMonth = RandomUtil.getRandomDateSameMonth(seedDate) + const ts = DateUtil.dateTimeToUnixFloat(randomDayWithinSameMonth) + const putResult = await doPut( + SNode1Constants.apiUrl, + SNode1Constants.namespace, + nsIndex, + ts, + key, + dataToDb + ) } - console.log("-->finished test", testCounter, " elapsed ", (Date.now() - startTs) / 1000.0); - expect(isEqual).equals(true); - return Promise.resolve(getResult.status); -} - -describe('snode-full', function () { - const pg = pgPromise({}); - const snodeDb = pg(SNode1Constants.dbUri); - it('snode-init', async function () { - this.timeout(30000); - await cleanAllTablesAndInitNetworkStorageLayout(snodeDb); - }) - - it('snode-put-get', async function () { - this.timeout(30000); - - let promiseArr = []; - console.log('PARALLEL_THREADS=', process.env.PARALLEL_THREADS); - let parallelThreads = process.env.PARALLEL_THREADS || 50; - for (let i = 0; i < parallelThreads; i++) { - try { - promiseArr.push(performOneTest(SNode1Constants.apiUrl, SNode1Constants.namespace, i)); // TODO !!!!!!!!! - } catch (e) { - console.log("failed to submit thread#", i); - } - - } - let result = await PromiseUtil.allSettled(promiseArr); - console.log(result); - for (const p of promiseArr) { - try { - await p; - } catch (e) { - console.log('failed to await promise', e) - } - console.log(p.state); - } - }); - - it('snode-test-list', async function () { - const numOfRowsToGenerate = 37; - const seedDate = DateUtil.buildDateTime(2015, 1, 22); - // this is almost unique inbox, so it's empty - const nsIndex = '' + RandomUtil.getRandomInt(1000, 10000000000000000); - // Generate data within the same month, only days are random - for (let i = 0; i < numOfRowsToGenerate; i++) { - const key = crypto.randomUUID(); - const dataToDb = { - name: 'john' + '1', - surname: crypto.randomBytes(10).toString('base64'), - id: RandomUtil.getRandomInt(0, 100000) - }; - let randomDayWithinSameMonth = RandomUtil.getRandomDateSameMonth(seedDate); - const ts = DateUtil.dateTimeToUnixFloat(randomDayWithinSameMonth); - let putResult = await doPut(SNode1Constants.apiUrl, SNode1Constants.namespace, nsIndex, - ts, key, dataToDb); - - } - - // Query Generated rows and count them - let month = DateUtil.formatYYYYMM(seedDate); - let resp; - let itemsLength; - let query = 0; - let firstTs; - let totalRowsFetched = 0; - do { - resp = await doList(SNode1Constants.apiUrl, SNode1Constants.namespace, nsIndex, month, firstTs); - query++; - itemsLength = resp?.data?.items?.length || 0; - totalRowsFetched += itemsLength; - let lastTs = resp?.data?.lastTs; - console.log('query', query, `got `, itemsLength, 'items, lastTs = ', lastTs); - firstTs = lastTs; - // await PromiseUtil.sleep(10000); - } while ((resp?.data?.items?.length || 0) > 0) - - console.log('total rows fetched ', totalRowsFetched); - - // Compare rows in DB (storageTable) vs rows fetched via API - const shardId = DbHelper.calculateShardForNamespaceIndex(SNode1Constants.namespace, nsIndex); - const storageTable = await DbHelper.findStorageTableByDate(SNode1Constants.namespace, shardId, seedDate); - const rowCount = await snodeDb.one(`SELECT count(*) as count + // Query Generated rows and count them + const month = DateUtil.formatYYYYMM(seedDate) + let resp + let itemsLength + let query = 0 + let firstTs + let totalRowsFetched = 0 + do { + resp = await doList( + SNode1Constants.apiUrl, + SNode1Constants.namespace, + nsIndex, + month, + firstTs + ) + query++ + itemsLength = resp?.data?.items?.length || 0 + totalRowsFetched += itemsLength + const lastTs = resp?.data?.lastTs + console.log('query', query, `got `, itemsLength, 'items, lastTs = ', lastTs) + firstTs = lastTs + // await PromiseUtil.sleep(10000); + } while ((resp?.data?.items?.length || 0) > 0) + + console.log('total rows fetched ', totalRowsFetched) + + // Compare rows in DB (storageTable) vs rows fetched via API + const shardId = DbHelper.calculateShardForNamespaceIndex(SNode1Constants.namespace, nsIndex) + const storageTable = await DbHelper.findStorageTableByDate( + SNode1Constants.namespace, + shardId, + seedDate + ) + const rowCount = await snodeDb + .one( + `SELECT count(*) as count FROM ${storageTable} - WHERE namespace_id='${nsIndex}'`) - .then(value => Number.parseInt(value.count)); - console.log(`${storageTable} contains ${rowCount} rows`); - expect(rowCount).equals(totalRowsFetched); - }); + WHERE namespace_id='${nsIndex}'` + ) + .then((value) => Number.parseInt(value.count)) + console.log(`${storageTable} contains ${rowCount} rows`) + expect(rowCount).equals(totalRowsFetched) + }) }) diff --git a/tests/VNodeIntegration.test.ts b/tests/VNodeIntegration.test.ts index f308d06..5b0e21c 100644 --- a/tests/VNodeIntegration.test.ts +++ b/tests/VNodeIntegration.test.ts @@ -1,149 +1,161 @@ +import { AxiosResponse } from 'axios' import chai from 'chai' -import {AxiosResponse} from "axios"; // import crypto from 'crypto' -const crypto = require("crypto"); -var _ = require('lodash'); +const crypto = require('crypto') +const _ = require('lodash') const expect = chai.expect -import assert from 'assert-ts'; -const axios = require('axios'); -import { RandomUtil, PromiseUtil, EnvLoader, DateUtil, VNodeClient } from 'dstorage-common' -import {DateTime} from "ts-luxon"; -import DbHelper from "../src/helpers/dbHelper"; +const axios = require('axios') +import { DateUtil, EnvLoader, PromiseUtil, RandomUtil, VNodeClient } from 'dstorage-common' +import { DateTime } from 'ts-luxon' - -EnvLoader.loadEnvOrFail(); +EnvLoader.loadEnvOrFail() class VNode1Constants { - // API TESTS - static apiUrl = EnvLoader.getPropertyOrFail('TEST_VNODE1_API_URL'); - static namespace = 'feeds'; + // API TESTS + static apiUrl = EnvLoader.getPropertyOrFail('TEST_VNODE1_API_URL') + static namespace = 'feeds' } -let vnodeClient = new VNodeClient(); +const vnodeClient = new VNodeClient() describe('vnode-full', function () { - - it('vnode-put-get', async function () { - this.timeout(30000); - - let promiseArr = []; - console.log('PARALLEL_THREADS=', process.env.PARALLEL_THREADS); - let parallelThreads = process.env.PARALLEL_THREADS || 1; - for (let i = 0; i < parallelThreads; i++) { - try { - promiseArr.push(performOneTest(VNode1Constants.apiUrl, VNode1Constants.namespace, i)); - } catch (e) { - console.log("failed to submit thread#", i); - } - - } - let result = await PromiseUtil.allSettled(promiseArr); - console.log(result); - for (const p of promiseArr) { - try { - await p; - } catch (e) { - console.log('failed to await promise', e) - } - console.log(p.state); - } - }) - - it('vnode-test-list', async function () { - const numOfRowsToGenerate = 37; - const seedDate = DateUtil.buildDateTime(2015, 1, 22); - // this is almost unique inbox, so it's empty - const nsIndex = '' + RandomUtil.getRandomInt(1000, 10000000000000000); - // Generate data within the same month, only days are random - let storedKeysSet = new Set(); - for (let i = 0; i < numOfRowsToGenerate; i++) { - const key = crypto.randomUUID(); - storedKeysSet.add(key); - const dataToDb = { - name: 'john' + '1', - surname: crypto.randomBytes(10).toString('base64'), - id: RandomUtil.getRandomInt(0, 100000) - }; - let randomDayWithinSameMonth = RandomUtil.getRandomDateSameMonth(seedDate); - const ts = DateUtil.dateTimeToUnixFloat(randomDayWithinSameMonth); - let putResult = await vnodeClient.postRecord(VNode1Constants.apiUrl, VNode1Constants.namespace, nsIndex, - ts + '', key, dataToDb); - - } - - // Query Generated rows and count them - let month = DateUtil.formatYYYYMM(seedDate); - let resp: AxiosResponse; - let itemsLength; - let query = 0; - let firstTs = null; - let totalRowsFetched = 0; - do { - resp = await vnodeClient.listRecordsByMonth(VNode1Constants.apiUrl, VNode1Constants.namespace, nsIndex, month, firstTs); - query++; - itemsLength = resp?.data?.items?.length || 0; - totalRowsFetched += itemsLength; - let lastTs = resp?.data?.result?.lastTs; - console.log('query', query, `got `, itemsLength, 'items, lastTs = ', lastTs); - firstTs = lastTs - for (let item of resp?.data?.items || []) { - let success = storedKeysSet.delete(item.skey); - console.log(`correct key ${item.skey} , success=${success}`); - } - // await PromiseUtil.sleep(10000); - } while ((resp?.data?.items?.length || 0) > 0) - - console.log('total rows fetched ', totalRowsFetched); - - // Compare rows stored vs rows fetched back via API - expect(storedKeysSet.size).equals(0); - - - }); - -}); - -async function performOneTest(baseUri: string, ns: string, testCounter: number): Promise { - const startTs = Date.now(); - console.log("-->started test", testCounter); - let key = crypto.randomUUID(); - let dataToDb = { + it('vnode-put-get', async function () { + this.timeout(30000) + + const promiseArr = [] + console.log('PARALLEL_THREADS=', process.env.PARALLEL_THREADS) + const parallelThreads = process.env.PARALLEL_THREADS || 1 + for (let i = 0; i < parallelThreads; i++) { + try { + promiseArr.push(performOneTest(VNode1Constants.apiUrl, VNode1Constants.namespace, i)) + } catch (e) { + console.log('failed to submit thread#', i) + } + } + const result = await PromiseUtil.allSettled(promiseArr) + console.log(result) + for (const p of promiseArr) { + try { + await p + } catch (e) { + console.log('failed to await promise', e) + } + console.log(p.state) + } + }) + + it('vnode-test-list', async function () { + const numOfRowsToGenerate = 37 + const seedDate = DateUtil.buildDateTime(2015, 1, 22) + // this is almost unique inbox, so it's empty + const nsIndex = '' + RandomUtil.getRandomInt(1000, 10000000000000000) + // Generate data within the same month, only days are random + const storedKeysSet = new Set() + for (let i = 0; i < numOfRowsToGenerate; i++) { + const key = crypto.randomUUID() + storedKeysSet.add(key) + const dataToDb = { name: 'john' + '1', surname: crypto.randomBytes(10).toString('base64'), id: RandomUtil.getRandomInt(0, 100000) - }; - - let nsIndex = RandomUtil.getRandomInt(0, 100000).toString(); - - let dateObj = RandomUtil.getRandomDate(DateTime.fromObject({year: 2022, month: 1, day: 1}), - DateTime.fromObject({year: 2022, month: 12, day: 31})) - if (!dateObj.isValid) { - console.log("INVALID DATE !!! ", dateObj); - return + } + const randomDayWithinSameMonth = RandomUtil.getRandomDateSameMonth(seedDate) + const ts = DateUtil.dateTimeToUnixFloat(randomDayWithinSameMonth) + const putResult = await vnodeClient.postRecord( + VNode1Constants.apiUrl, + VNode1Constants.namespace, + nsIndex, + ts + '', + key, + dataToDb + ) } - let dateFormatted = dateObj.toFormat('yyyyMMdd'); - console.log('dateFormatted', dateFormatted); - let putResult = await vnodeClient.postRecord(baseUri, ns, nsIndex, dateObj.toUnixInteger() + '', key, dataToDb); - if (putResult.status != 201) { - console.log('PUT ERROR!!! ', putResult.status); - return; - } - let getResult = await vnodeClient.getRecord(baseUri, ns, nsIndex, dateFormatted, key); - if (getResult.status != 200) { - console.log('GET ERROR!!! ', getResult.status); - return; - } - let dataFromDb = getResult.data.items[0].payload; - let isEqual = _.isEqual(dataToDb, dataFromDb); - if (!isEqual) { - console.log(`isEqual = `, isEqual); - console.log('dataToDb', dataToDb, 'dataFromDb', dataFromDb); - } - expect(getResult.data.result.quorumResult).equals('QUORUM_OK'); - console.log("-->finished test", testCounter, " elapsed ", (Date.now() - startTs) / 1000.0); - expect(isEqual).equals(true); - return Promise.resolve(getResult.status); + // Query Generated rows and count them + const month = DateUtil.formatYYYYMM(seedDate) + let resp: AxiosResponse + let itemsLength + let query = 0 + let firstTs = null + let totalRowsFetched = 0 + do { + resp = await vnodeClient.listRecordsByMonth( + VNode1Constants.apiUrl, + VNode1Constants.namespace, + nsIndex, + month, + firstTs + ) + query++ + itemsLength = resp?.data?.items?.length || 0 + totalRowsFetched += itemsLength + const lastTs = resp?.data?.result?.lastTs + console.log('query', query, `got `, itemsLength, 'items, lastTs = ', lastTs) + firstTs = lastTs + for (const item of resp?.data?.items || []) { + const success = storedKeysSet.delete(item.skey) + console.log(`correct key ${item.skey} , success=${success}`) + } + // await PromiseUtil.sleep(10000); + } while ((resp?.data?.items?.length || 0) > 0) + + console.log('total rows fetched ', totalRowsFetched) + + // Compare rows stored vs rows fetched back via API + expect(storedKeysSet.size).equals(0) + }) +}) + +async function performOneTest(baseUri: string, ns: string, testCounter: number): Promise { + const startTs = Date.now() + console.log('-->started test', testCounter) + const key = crypto.randomUUID() + const dataToDb = { + name: 'john' + '1', + surname: crypto.randomBytes(10).toString('base64'), + id: RandomUtil.getRandomInt(0, 100000) + } + + const nsIndex = RandomUtil.getRandomInt(0, 100000).toString() + + const dateObj = RandomUtil.getRandomDate( + DateTime.fromObject({ year: 2022, month: 1, day: 1 }), + DateTime.fromObject({ year: 2022, month: 12, day: 31 }) + ) + if (!dateObj.isValid) { + console.log('INVALID DATE !!! ', dateObj) + return + } + const dateFormatted = dateObj.toFormat('yyyyMMdd') + console.log('dateFormatted', dateFormatted) + + const putResult = await vnodeClient.postRecord( + baseUri, + ns, + nsIndex, + dateObj.toUnixInteger() + '', + key, + dataToDb + ) + if (putResult.status != 201) { + console.log('PUT ERROR!!! ', putResult.status) + return + } + const getResult = await vnodeClient.getRecord(baseUri, ns, nsIndex, dateFormatted, key) + if (getResult.status != 200) { + console.log('GET ERROR!!! ', getResult.status) + return + } + const dataFromDb = getResult.data.items[0].payload + const isEqual = _.isEqual(dataToDb, dataFromDb) + if (!isEqual) { + console.log(`isEqual = `, isEqual) + console.log('dataToDb', dataToDb, 'dataFromDb', dataFromDb) + } + expect(getResult.data.result.quorumResult).equals('QUORUM_OK') + console.log('-->finished test', testCounter, ' elapsed ', (Date.now() - startTs) / 1000.0) + expect(isEqual).equals(true) + return Promise.resolve(getResult.status) } diff --git a/tests/root.ts b/tests/root.ts index 1677cc5..4d4dbf8 100755 --- a/tests/root.ts +++ b/tests/root.ts @@ -1,10 +1,10 @@ -import chalk from 'chalk'; +import chalk from 'chalk' export const mochaHooks = { // This file is needed to end the test suite. afterAll(done) { - done(); - console.log(chalk.bold.green.inverse(' ALL TEST CASES EXECUTED ')); - process.exit(0); + done() + console.log(chalk.bold.green.inverse(' ALL TEST CASES EXECUTED ')) + process.exit(0) } -}; \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 2425382..6d079a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -234,6 +234,38 @@ enabled "2.0.x" kuler "^2.0.0" +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + "@ethereumjs/common@^2.3.0", "@ethereumjs/common@^2.4.0", "@ethereumjs/common@^2.6.4": version "2.6.5" resolved "https://registry.yarnpkg.com/@ethereumjs/common/-/common-2.6.5.tgz#0a75a22a046272579d91919cb12d84f2756e8d30" @@ -744,6 +776,25 @@ lodash.camelcase "^4.3.0" protobufjs "^6.8.6" +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@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@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + "@ipld/dag-cbor@^6.0.3": version "6.0.15" resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-6.0.15.tgz#aebe7a26c391cae98c32faedb681b1519e3d2372" @@ -979,9 +1030,9 @@ tar "^6.1.11" "@mongodb-js/saslprep@^1.1.5": - version "1.1.7" - resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz#d1700facfd6916c50c2c88fd6d48d363a56c702f" - integrity sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q== + version "1.1.8" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.1.8.tgz#d39744540be8800d17749990b0da95b4271840d1" + integrity sha512-qKwC/M/nNNaKUBMQ0nuzm47b7ZYWQHN3pcXq4IIcoSBc2hOIrflAxJduIvvqmhoz3gR2TacTAs8vlsCVPkiEdQ== dependencies: sparse-bitfield "^3.0.3" @@ -990,6 +1041,27 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== +"@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" + "@phc/format@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@phc/format/-/format-1.0.0.tgz#b5627003b3216dc4362125b13f48a4daa76680e4" @@ -1236,11 +1308,6 @@ dependencies: "@types/node" "*" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - "@types/express-serve-static-core@^4.17.33": version "4.19.5" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" @@ -1303,11 +1370,6 @@ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.14.tgz#37daaf78069e7948520474c87b80092ea912520a" integrity sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug== -"@types/json-schema@^7.0.3": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - "@types/keyv@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" @@ -1491,49 +1553,97 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^1.7.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz#22fed9b16ddfeb402fd7bcde56307820f6ebc49f" - integrity sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g== +"@typescript-eslint/eslint-plugin@^7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz#f5f5da52db674b1f2cdb9d5f3644e5b2ec750465" + integrity sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/type-utils" "7.16.1" + "@typescript-eslint/utils" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.16.1.tgz#84c581cf86c8b2becd48d33ddc41a6303d57b274" + integrity sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA== dependencies: - "@typescript-eslint/experimental-utils" "1.13.0" - eslint-utils "^1.3.1" - functional-red-black-tree "^1.0.1" - regexpp "^2.0.1" - tsutils "^3.7.0" + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/typescript-estree" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" + debug "^4.3.4" -"@typescript-eslint/experimental-utils@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e" - integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg== +"@typescript-eslint/scope-manager@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz#2b43041caabf8ddd74512b8b550b9fc53ca3afa1" + integrity sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw== dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "1.13.0" - eslint-scope "^4.0.0" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" -"@typescript-eslint/parser@^1.7.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-1.13.0.tgz#61ac7811ea52791c47dc9fd4dd4a184fae9ac355" - integrity sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ== +"@typescript-eslint/type-utils@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz#4d7ae4f3d9e3c8cbdabae91609b1a431de6aa6ca" + integrity sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA== dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "1.13.0" - "@typescript-eslint/typescript-estree" "1.13.0" - eslint-visitor-keys "^1.0.0" + "@typescript-eslint/typescript-estree" "7.16.1" + "@typescript-eslint/utils" "7.16.1" + debug "^4.3.4" + ts-api-utils "^1.3.0" -"@typescript-eslint/typescript-estree@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e" - integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw== +"@typescript-eslint/types@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.16.1.tgz#bbab066276d18e398bc64067b23f1ce84dfc6d8c" + integrity sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ== + +"@typescript-eslint/typescript-estree@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz#9b145ba4fd1dde1986697e1ce57dc501a1736dd3" + integrity sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ== dependencies: - lodash.unescape "4.0.1" - semver "5.5.0" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/visitor-keys" "7.16.1" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.16.1.tgz#df42dc8ca5a4603016fd102db0346cdab415cdb7" + integrity sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.16.1" + "@typescript-eslint/types" "7.16.1" + "@typescript-eslint/typescript-estree" "7.16.1" + +"@typescript-eslint/visitor-keys@7.16.1": + version "7.16.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz#4287bcf44c34df811ff3bb4d269be6cfc7d8c74b" + integrity sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg== + dependencies: + "@typescript-eslint/types" "7.16.1" + eslint-visitor-keys "^3.4.3" "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + JSONStream@0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-0.10.0.tgz#74349d0d89522b71f30f0a03ff9bd20ca6f12ac0" @@ -1583,6 +1693,11 @@ acorn-globals@^4.1.0: acorn "^6.0.1" acorn-walk "^6.0.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@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" @@ -1615,7 +1730,7 @@ acorn@^6.0.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^8.11.0, acorn@^8.4.1: +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -1680,7 +1795,7 @@ agent-base@~4.2.1: dependencies: es6-promisify "^5.0.0" -ajv@^6.12.3: +ajv@^6.12.3, 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== @@ -1719,6 +1834,11 @@ ansi-escapes@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== +ansi-escapes@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-6.2.1.tgz#76c54ce9b081dad39acec4b5d53377913825fb0f" + integrity sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig== + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -1739,6 +1859,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -1758,6 +1883,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^6.0.0, ansi-styles@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + any-promise@^1.0.0, any-promise@^1.1.0, any-promise@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -1871,6 +2001,11 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +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== + array-uniq@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" @@ -2325,6 +2460,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -2341,7 +2483,7 @@ braces@^2.3.1: split-string "^3.0.2" to-regex "^3.0.1" -braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -2785,7 +2927,7 @@ chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0, chalk@^4.1.2: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2793,6 +2935,11 @@ chalk@^4.1.0, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + charset@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/charset/-/charset-1.0.1.tgz#8d59546c355be61049a8fa9164747793319852bd" @@ -2901,6 +3048,13 @@ cli-cursor@^1.0.2: dependencies: restore-cursor "^1.0.1" +cli-cursor@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" + integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== + dependencies: + restore-cursor "^4.0.0" + cli-truncate@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-1.1.0.tgz#2b2dfd83c53cfd3572b87fc4d430a808afb04086" @@ -2909,6 +3063,14 @@ cli-truncate@^1.0.0: slice-ansi "^1.0.0" string-width "^2.0.0" +cli-truncate@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" + integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== + dependencies: + slice-ansi "^5.0.0" + string-width "^7.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -3002,6 +3164,11 @@ color@^3.1.3: color-convert "^1.9.3" color-string "^1.6.0" +colorette@^2.0.20: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + colorspace@1.1.x: version "1.1.4" resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" @@ -3030,6 +3197,11 @@ commander@^2.12.1, commander@^2.9.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@~12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + component-emitter@^1.2.1, component-emitter@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" @@ -3272,6 +3444,15 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" +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" + cryptiles@4.x.x: version "4.1.3" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-4.1.3.tgz#2461d3390ea0b82c643a6ba79f0ed491b0934c25" @@ -3433,7 +3614,7 @@ debug@3.1.0, debug@~3.1.0: dependencies: ms "2.0.0" -debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: +debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== @@ -3542,7 +3723,7 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@~0.1.3: +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== @@ -3700,6 +3881,13 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" +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" + dns-over-http-resolver@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/dns-over-http-resolver/-/dns-over-http-resolver-1.2.3.tgz#194d5e140a42153f55bb79ac5a64dd2768c36af9" @@ -3719,6 +3907,13 @@ docker-modem@^0.3.1: readable-stream "~1.0.26-4" split-ca "^1.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" + dom-walk@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" @@ -3904,6 +4099,11 @@ elliptic@^6.4.0, elliptic@^6.4.1, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6. minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emoji-regex@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" + integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -4160,7 +4360,7 @@ escape-html@1.0.3, escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@4.0.0: +escape-string-regexp@4.0.0, 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== @@ -4201,25 +4401,79 @@ eslint-plugin-prettier@^3.0.1: dependencies: prettier-linter-helpers "^1.0.0" -eslint-scope@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" - integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== +eslint-plugin-simple-import-sort@^12.1.1: + version "12.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz#e64bfdaf91c5b98a298619aa634a9f7aa43b709e" + integrity sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA== + +eslint-plugin-unused-imports@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.0.0.tgz#93f3a7ee6088221e4a1d7127866e05d5917a9f65" + integrity sha512-mzM+y2B7XYpQryVa1usT+Y/BdNAtAZiXzwpSyDCboFoJN/LZRN67TNvQxKtuTK/Aplya3sLNQforiubzPPaIcQ== dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" + eslint-rule-composer "^0.3.0" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== -eslint-utils@^1.3.1: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: - eslint-visitor-keys "^1.1.0" + esrecurse "^4.3.0" + estraverse "^5.2.0" -eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.7.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" esniff@^2.0.1: version "2.0.1" @@ -4231,6 +4485,15 @@ esniff@^2.0.1: event-emitter "^0.3.5" type "^2.7.2" +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@3.x.x: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -4241,19 +4504,26 @@ esprima@^4.0.0, esprima@^4.0.1: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esrecurse@^4.1.0: +esquery@^1.4.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + 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, estraverse@^4.2.0: +estraverse@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.2.0: +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== @@ -4551,6 +4821,11 @@ eventemitter3@^3.1.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + events@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -4595,6 +4870,36 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^5.1.1: + 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" + +execa@~8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" @@ -4745,7 +5050,7 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== -fast-deep-equal@^3.1.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== @@ -4760,12 +5065,23 @@ fast-fifo@^1.0.0: resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + 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, 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== @@ -4780,6 +5096,13 @@ fast-text-encoding@^1.0.0: resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + faye-websocket@0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" @@ -4799,6 +5122,13 @@ fecha@^4.2.0: resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== +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" + file-uri-to-path@1, file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -4841,7 +5171,7 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-up@5.0.0: +find-up@5.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== @@ -4885,11 +5215,25 @@ firebase-admin@8.12.1: "@google-cloud/firestore" "^3.0.0" "@google-cloud/storage" "^4.1.2" +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + fn.name@1.x.x: version "1.1.0" resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" @@ -5126,6 +5470,11 @@ get-caller-file@^2.0.1, get-caller-file@^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-east-asian-width@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e" + integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== + get-func-name@^2.0.1, get-func-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41" @@ -5176,6 +5525,16 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +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== + +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.2.tgz#533744d5aa20aca4e079c8e5daf7fd44202821f5" @@ -5209,13 +5568,20 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@~5.1.2: +glob-parent@^5.1.2, 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.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -5260,6 +5626,13 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + globalthis@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" @@ -5268,6 +5641,18 @@ globalthis@^1.0.3: define-properties "^1.2.1" gopd "^1.0.1" +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" + google-auth-library@^5.0.0, google-auth-library@^5.5.0: version "5.10.1" resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-5.10.1.tgz#504ec75487ad140e68dd577c21affa363c87ddff" @@ -5374,6 +5759,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.4 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + graphql-request@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-4.3.0.tgz#b934e08fcae764aa2cdc697d3c821f046cb5dbf2" @@ -5726,6 +6116,21 @@ human-interval@~1.0.0: resolved "https://registry.yarnpkg.com/human-interval/-/human-interval-1.0.0.tgz#7ba00a15f3d94ab6a4c16f76060e4aa07c713019" integrity sha512-SWPw3rD6/ocA0JnGePoXp5Zf5eILzsoL5vdWdLwtTuyrElyCpfQb0whIcxMdK/gAKNl2rFDGkPAbwI2KGZCvNA== +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== + +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + +husky@^9.0.11: + version "9.0.11" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.0.11.tgz#fc91df4c756050de41b3e478b2158b87c1e79af9" + integrity sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw== + iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -5762,6 +6167,11 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== +ignore@^5.2.0, ignore@^5.3.0, ignore@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + image-size@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.8.3.tgz#f0b568857e034f29baffd37013587f2c0cad8b46" @@ -5769,6 +6179,14 @@ image-size@0.8.3: dependencies: queue "6.0.1" +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-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -6083,9 +6501,9 @@ is-ci@^2.0.0: ci-info "^2.0.0" is-core-module@^2.13.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" - integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== dependencies: hasown "^2.0.2" @@ -6163,6 +6581,18 @@ is-fullwidth-code-point@^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-fullwidth-code-point@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== + +is-fullwidth-code-point@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz#9609efced7c2f97da7b60145ef481c787c7ba704" + integrity sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA== + dependencies: + get-east-asian-width "^1.0.0" + is-function@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" @@ -6180,7 +6610,7 @@ is-generator-function@^1.0.7: dependencies: has-tostringtag "^1.0.0" -is-glob@^4.0.1, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -6273,6 +6703,11 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +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-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -6342,6 +6777,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -6955,7 +7395,7 @@ js-sha3@0.8.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@4.1.0: +js-yaml@4.1.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== @@ -7044,6 +7484,11 @@ json-schema@0.4.0: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== +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== + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -7202,7 +7647,7 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" -keyv@^4.0.0: +keyv@^4.0.0, keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -7255,6 +7700,14 @@ leven@^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" @@ -7263,11 +7716,44 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lilconfig@~3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" + integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== + +lint-staged@^15.2.7: + version "15.2.7" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.7.tgz#97867e29ed632820c0fb90be06cd9ed384025649" + integrity sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw== + dependencies: + chalk "~5.3.0" + commander "~12.1.0" + debug "~4.3.4" + execa "~8.0.1" + lilconfig "~3.1.1" + listr2 "~8.2.1" + micromatch "~4.0.7" + pidtree "~0.6.0" + string-argv "~0.3.2" + yaml "~2.4.2" + listify@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/listify/-/listify-1.0.3.tgz#a9335ac351c3d1aea515494ed746976eeb92248b" integrity sha512-083swF7iH7bx8666zdzBColpgEuy46HjN3r1isD4zV6Ix7FuHfb/2/WVnl4CH8hjuoWeFF7P5KkKNXUnJCFEJg== +listr2@~8.2.1: + version "8.2.3" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.3.tgz#c494bb89b34329cf900e4e0ae8aeef9081d7d7a5" + integrity sha512-Lllokma2mtoniUOS94CcOErHWAug5iu7HOmDrvWgpw8jyQH2fomgB+7lZS4HWZxytUuQwkGOwe49FvwVaA85Xw== + dependencies: + cli-truncate "^4.0.0" + colorette "^2.0.20" + eventemitter3 "^5.0.1" + log-update "^6.0.0" + rfdc "^1.4.1" + wrap-ansi "^9.0.0" + load-json-file@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" @@ -7369,6 +7855,11 @@ lodash.memoize@4.x: 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.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -7384,11 +7875,6 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== -lodash.unescape@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c" - integrity sha512-DhhGRshNS1aX6s5YdBE3njCCouPgnG29ebyHvImlZzXZf2SHgt+J08DHgytTPnpywNbO1Y8mNUFyQuIDBq2JZg== - lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -7410,6 +7896,17 @@ log-update@^1.0.2: ansi-escapes "^1.0.0" cli-cursor "^1.0.2" +log-update@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.0.0.tgz#0ddeb7ac6ad658c944c1de902993fce7c33f5e59" + integrity sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw== + dependencies: + ansi-escapes "^6.2.0" + cli-cursor "^4.0.0" + slice-ansi "^7.0.0" + strip-ansi "^7.1.0" + wrap-ansi "^9.0.0" + logform@^2.6.0, logform@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.1.tgz#71403a7d8cae04b2b734147963236205db9b3df0" @@ -7634,6 +8131,11 @@ merge-stream@^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== + method-override@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/method-override/-/method-override-3.0.0.tgz#6ab0d5d574e3208f15b0c9cf45ab52000468d7a2" @@ -7668,6 +8170,14 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +micromatch@^4.0.4, micromatch@~4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -7708,6 +8218,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -7750,13 +8265,20 @@ minimatch@4.2.1: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, 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" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist-options@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-3.0.2.tgz#fba4c8191339e13ecf4d61beb03f070103f3d954" @@ -8011,6 +8533,11 @@ mquery@5.0.0: dependencies: debug "4.x" +mri@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -8345,6 +8872,20 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" +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" + +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + npmlog@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" @@ -8492,13 +9033,20 @@ onetime@^1.0.0: resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" integrity sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A== -onetime@^5.1.0: +onetime@^5.1.0, 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" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + openpgp@^5.3.1: version "5.11.2" resolved "https://registry.yarnpkg.com/openpgp/-/openpgp-5.11.2.tgz#2c035a26b13feb3b0bb5180718ec91c8e65cc686" @@ -8530,6 +9078,18 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -8662,6 +9222,13 @@ packet-reader@1.0.0: resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== +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-asn1@^5.0.0, parse-asn1@^5.1.7: version "5.1.7" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.7.tgz#73cdaaa822125f9647165625eb45f8a051d2df06" @@ -8753,6 +9320,16 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== +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-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -8786,6 +9363,11 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" +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== + path@^0.11.14: version "0.11.14" resolved "https://registry.yarnpkg.com/path/-/path-0.11.14.tgz#cbc7569355cb3c83afeb4ace43ecff95231e5a7d" @@ -8894,11 +9476,21 @@ picocolors@^1.0.0, picocolors@^1.0.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== -picomatch@^2.0.4, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516" + integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag== + +pidtree@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" + integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -9036,6 +9628,11 @@ postgres-interval@^1.1.0: dependencies: xtend "^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" @@ -9058,10 +9655,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^1.17.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== pretty-format@^24.9.0: version "24.9.0" @@ -9073,6 +9670,19 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" +pretty-quick@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-4.0.0.tgz#ea5cce85a5804bfbec7327b0e064509155d03f39" + integrity sha512-M+2MmeufXb/M7Xw3Afh1gxcYpj+sK0AxEfnfF958ktFeAyi5MsKY5brymVURQLgPLV1QaF5P4pb2oFJ54H3yzQ== + dependencies: + execa "^5.1.1" + find-up "^5.0.0" + ignore "^5.3.0" + mri "^1.2.0" + picocolors "^1.0.0" + picomatch "^3.0.1" + tslib "^2.6.2" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -9258,6 +9868,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== +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== + queue@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/queue/-/queue-6.0.1.tgz#abd5a5b0376912f070a25729e0b6a7d565683791" @@ -9501,11 +10116,6 @@ regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: es-errors "^1.3.0" set-function-name "^2.0.1" -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - registry-auth-token@^3.0.1: version "3.4.0" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" @@ -9640,6 +10250,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw== +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-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -9681,6 +10296,14 @@ restore-cursor@^1.0.1: exit-hook "^1.0.0" onetime "^1.0.0" +restore-cursor@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9" + integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -9699,6 +10322,16 @@ retry-request@^4.0.0: debug "^4.1.1" extend "^3.0.2" +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== + +rfdc@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + rimraf@^2.4.4, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -9733,6 +10366,13 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== +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-array-concat@^1.0.0, safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" @@ -9892,17 +10532,12 @@ semver-diff@^2.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== - semver@^6.0.0, semver@^6.2.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.8: +semver@^7.3.5, semver@^7.3.8, semver@^7.6.0: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -10031,11 +10666,23 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +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@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== +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== + shellwords@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" @@ -10069,11 +10716,16 @@ sift@17.1.3: resolved "https://registry.yarnpkg.com/sift/-/sift-17.1.3.tgz#9d2000d4d41586880b0079b5183d839c7a142bf7" integrity sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ== -signal-exit@^3.0.0, signal-exit@^3.0.2: +signal-exit@^3.0.0, 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== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-concat@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" @@ -10112,6 +10764,11 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + slice-ansi@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" @@ -10119,6 +10776,22 @@ slice-ansi@^1.0.0: dependencies: is-fullwidth-code-point "^2.0.0" +slice-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== + dependencies: + ansi-styles "^6.0.0" + is-fullwidth-code-point "^4.0.0" + +slice-ansi@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9" + integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg== + dependencies: + ansi-styles "^6.2.1" + is-fullwidth-code-point "^5.0.0" + sliced@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" @@ -10432,6 +11105,11 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ== +string-argv@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -10471,6 +11149,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + string.prototype.trim@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" @@ -10546,6 +11233,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -10563,6 +11257,16 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== +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-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-hex-prefix@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" @@ -10582,7 +11286,7 @@ strip-indent@^2.0.0: resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" integrity sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA== -strip-json-comments@3.1.1: +strip-json-comments@3.1.1, 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== @@ -10748,6 +11452,11 @@ text-hex@1.0.x: resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== +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== + thenify@^3.1.0, thenify@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" @@ -10913,6 +11622,11 @@ triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + ts-jest@^24.0.0: version "24.3.0" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.3.0.tgz#b97814e3eab359ea840a1ac112deae68aa440869" @@ -10995,7 +11709,7 @@ tslib@^1.11.1, tslib@^1.8.0, tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1: +tslib@^2.0.1, tslib@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== @@ -11031,13 +11745,6 @@ tsutils@^2.29.0: dependencies: tslib "^1.8.1" -tsutils@^3.7.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" - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -11060,6 +11767,13 @@ tweetnacl@^1.0.3: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== +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" @@ -11072,6 +11786,11 @@ type-detect@^4.0.0, type-detect@^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-is@^1.6.4, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -11866,7 +12585,7 @@ which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, gopd "^1.0.1" has-tostringtag "^1.0.2" -which@2.0.2: +which@2.0.2, 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== @@ -11925,7 +12644,7 @@ with-callback@^1.0.2: resolved "https://registry.yarnpkg.com/with-callback/-/with-callback-1.0.2.tgz#a09629b9a920028d721404fb435bdcff5c91bc21" integrity sha512-zaUhn7OWgikdqWlPYpZ4rTX/6IAV0czMVyd+C6QLVrif2tATF28CYUnHBmHs2a5EaZo7bB1+plBUPHto+HW8uA== -word-wrap@~1.2.3: +word-wrap@^1.2.5, word-wrap@~1.2.3: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== @@ -11958,6 +12677,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e" + integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -12141,6 +12869,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@~2.4.2: + version "2.4.5" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e" + integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== + yargs-parser@10.x, yargs-parser@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8"