From 37aca45c9d91e806c7f8c4a326561521a395bb32 Mon Sep 17 00:00:00 2001 From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com> Date: Tue, 23 Apr 2024 17:35:36 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/uaw.md | 3 +- package.json | 2 + src/api.server.ts | 4 +- src/common/SyncLocker.ts | 66 ++++++++++++++++++++++++++ src/controllers/activity.controller.ts | 5 +- src/controllers/chain.controller.ts | 5 +- src/controllers/chest.controller.ts | 9 ++-- src/controllers/game.controller.ts | 13 ++--- src/controllers/lottery.controller.ts | 7 +-- src/controllers/nft.controller.ts | 7 +-- src/controllers/sign.controller.ts | 5 +- src/controllers/tasks.controller.ts | 7 +-- src/controllers/voucher.controller.ts | 5 +- yarn.lock | 57 ++++++++++++++++++++++ 14 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 src/common/SyncLocker.ts diff --git a/docs/uaw.md b/docs/uaw.md index ebc4f4e..0095c4a 100644 --- a/docs/uaw.md +++ b/docs/uaw.md @@ -58,7 +58,7 @@ #### 20240423 1. 签到列表接口(11)增加返回字段: 是否已领取奖励 1. 检查签到并领取奖励(23), 增加post参数, 可以领取指定天数的奖励 -1. 宝箱助力(18), 增加返回scoreBonus, 表示当前宝箱已得到的助力积分 +1. 宝箱助力(18), 宝箱助力状态查询(26) 增加返回scoreBonus, 表示当前宝箱已得到的助力积分 ### 1. 钱包预登录 @@ -803,6 +803,7 @@ body: chestMax: 10, // 宝箱最大可助力次数 nickname: '11', // 宝箱所有者昵称 avatar: '', // 宝箱所有者头像, 可能为空 + scoreBonus: 1 //当前箱子已助力积分 } ``` diff --git a/package.json b/package.json index 64293f9..c5cec3b 100644 --- a/package.json +++ b/package.json @@ -33,10 +33,12 @@ "fastify": "^4.15.0", "fastify-plugin": "^3.0.0", "fastify-xml-body-parser": "^2.2.0", + "ioredis": "^5.4.1", "mongodb-extended-json": "^1.11.1", "mongoose": "8.2.3", "nanoid": "^3.1.23", "node-schedule": "^2.0.0", + "redlock": "^5.0.0-beta.2", "siwe": "^2.1.4", "tracer": "^1.1.6", "zutils": "link:packages/zutils" diff --git a/src/api.server.ts b/src/api.server.ts index a5dd788..0d6f2bb 100644 --- a/src/api.server.ts +++ b/src/api.server.ts @@ -7,8 +7,8 @@ import { IncomingMessage, Server, ServerResponse } from 'http' import { mongoose } from '@typegoose/typegoose' import logger from 'logger/logger' import NonceRecordSchedule from 'schedule/noncerecord.schedule' -import { RouterMap, SyncLocker, ZRedisClient } from 'zutils' -import CacheSchedule from 'schedule/cache.schedule' +import { RouterMap, ZRedisClient } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' const zReqParserPlugin = require('plugins/zReqParser') diff --git a/src/common/SyncLocker.ts b/src/common/SyncLocker.ts new file mode 100644 index 0000000..ab8f972 --- /dev/null +++ b/src/common/SyncLocker.ts @@ -0,0 +1,66 @@ +import { singleton } from 'zutils' +import Client from 'ioredis' +import Redlock, { Lock, ResourceLockedError } from 'redlock' +import logger from 'logger/logger' + +interface IRequest { + method: string + url: string + user?: { + id: string + } + lock?: Lock +} + +const redisA = new Client(process.env.REDIS) +const redlock = new Redlock( + // You should have one client for each independent redis node + // or cluster. + [redisA], + { + // The expected clock drift; for more details see: + // http://redis.io/topics/distlock + driftFactor: 0.01, // multiplied by lock ttl to determine drift time + + // The max number of times Redlock will attempt to lock a resource + // before erroring. + retryCount: 10, + + // the time in ms between attempts + retryDelay: 200, // time in ms + + // the max time in ms randomly added to retries + // to improve performance under high contention + // see https://www.awsarchitectureblog.com/2015/03/backoff.html + retryJitter: 200, // time in ms + + // The minimum remaining time on a lock before an extension is automatically + // attempted with the `using` API. + automaticExtensionThreshold: 500, // time in ms + }, +) + +redlock.on('error', error => { + // Ignore cases where a resource is explicitly marked as locked on a client. + if (error instanceof ResourceLockedError) { + return + } + // Log all other errors. + logger.error(error) +}) + +@singleton +export class SyncLocker { + public unlock(req: IRequest) { + if (req.lock) { + req.lock.release() + } + } + + public async checkLock(req: IRequest) { + const key = `${req.method}:${req.url}:${req.user?.id || ''}` + let lock = await redlock.acquire([key], 3000) + req.lock = lock + return true + } +} diff --git a/src/controllers/activity.controller.ts b/src/controllers/activity.controller.ts index 99b73e0..34759d0 100644 --- a/src/controllers/activity.controller.ts +++ b/src/controllers/activity.controller.ts @@ -1,7 +1,8 @@ import { ActivityInfo } from 'models/ActivityInfo' import { ActivityUser } from 'models/ActivityUser' import { rankKey, rankLevel } from 'services/rank.svr' -import { BaseController, ROLE_ANON, SyncLocker, ZError, ZRedisClient, role, router } from 'zutils' +import { BaseController, ROLE_ANON, ZError, ZRedisClient, role, router } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' import { ScoreRecord } from 'models/ScoreRecord' import { formatAddress } from 'zutils/utils/chain.util' import { formatNumShow, isValidShareCode } from 'common/Utils' @@ -32,7 +33,7 @@ export default class ActivityController extends BaseController { */ @router('post /api/activity/upload_invite_code') async uploadInviteCode(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('upload_invite_code', req) await checkReCaptcha(req, 'invite_user') let { code } = req.params diff --git a/src/controllers/chain.controller.ts b/src/controllers/chain.controller.ts index ff1d02d..fcccdc3 100644 --- a/src/controllers/chain.controller.ts +++ b/src/controllers/chain.controller.ts @@ -1,4 +1,5 @@ -import { ZError, SyncLocker, BaseController, router } from 'zutils' +import { ZError, BaseController, router } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' import { FastifyRequest } from 'fastify' import { ActivityItem } from 'models/ActivityItem' import { TokenClaimHistory } from 'models/TokenClaimHistory' @@ -16,7 +17,7 @@ export default class ChainController extends BaseController { // @router('post /api/lottery/claim_usdt') async preClaimUsdt(req: FastifyRequest) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) let user = req.user if (user.address) { throw new ZError(11, 'no address') diff --git a/src/controllers/chest.controller.ts b/src/controllers/chest.controller.ts index ff69887..cb0e181 100644 --- a/src/controllers/chest.controller.ts +++ b/src/controllers/chest.controller.ts @@ -1,4 +1,5 @@ -import { ZError, SyncLocker, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils' +import { ZError, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' import { ActivityChest, ChestStatusEnum } from 'models/ActivityChest' import { ActivityUser } from 'models/ActivityUser' import { rankKey, rankLevel, updateRankScore } from 'services/rank.svr' @@ -185,6 +186,7 @@ class BoxController extends BaseController { avatar: chestOwner?.twitterAvatar || '', chestCurrent: chest.bonusUsers.length, chestMax: chest.maxBounsCount, + scoreBonus: chest.scoreBonus, } } else { return { @@ -196,6 +198,7 @@ class BoxController extends BaseController { ? formatAddress(chestOwner.address) : 'unknown', avatar: chestOwner?.twitterAvatar || '', + scoreBonus: chest.scoreBonus, } } } @@ -204,7 +207,7 @@ class BoxController extends BaseController { */ @router('post /api/chest/enhance') async enhance(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('enhance', req) await checkReCaptcha(req, 'chest_share') const { code } = req.params @@ -307,7 +310,7 @@ class BoxController extends BaseController { */ @router('post /api/chest/open') async openChest(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('open_chest', req) const user = req.user const { chestId } = req.params diff --git a/src/controllers/game.controller.ts b/src/controllers/game.controller.ts index d6e4cf1..f983378 100644 --- a/src/controllers/game.controller.ts +++ b/src/controllers/game.controller.ts @@ -3,7 +3,8 @@ import { ActivityGame } from 'models/ActivityGame' import { DAILY_SIGN, SIGN_SEQ, SIGN_TOTAL, TicketRecord, USE_TICKET } from 'models/TicketRecord' import { queryCheckInList } from 'services/chain.svr' import { checkInToday, seqSignCfg, seqSignScore, totalSignCfg, totalSignScore } from 'services/sign.svr' -import { ZError, SyncLocker, BaseController, router } from 'zutils' +import { ZError, BaseController, router } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' import { formatDate } from 'utils/utcdate.util' import { generateStepReward } from 'services/game.svr' import { updateRankScore } from 'services/rank.svr' @@ -23,7 +24,7 @@ class GameController extends BaseController { */ @router('post /api/user/checkin') async checkIn(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('checkin', req) const user = req.user const { address } = user @@ -99,7 +100,7 @@ class GameController extends BaseController { */ @router('post /api/user/checkin/claim') async claimCheckResult(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('claim_checkin_total', req) const user = req.user let { days } = req.params @@ -150,7 +151,7 @@ class GameController extends BaseController { */ @router('post /api/user/checkin/claim_seq') async claimCheckSeqResult(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('claim_checkin_seq', req) const user = req.user let { days } = req.params @@ -302,7 +303,7 @@ class GameController extends BaseController { @router('post /api/game/pre_step') async gameChest(req) { const user = req.user - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) let { step } = req.params step = step || '1' if (isNaN(step)) { @@ -335,7 +336,7 @@ class GameController extends BaseController { */ @router('post /api/game/step') async gameStep(req, res) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('step', req) const user = req.user const { id } = req.params diff --git a/src/controllers/lottery.controller.ts b/src/controllers/lottery.controller.ts index 6cbfa69..90681de 100644 --- a/src/controllers/lottery.controller.ts +++ b/src/controllers/lottery.controller.ts @@ -1,6 +1,7 @@ import { EMPTY_REWARD, SCORE_DRAW } from 'common/Constants' import { LotteryCache } from 'common/LotteryCache' -import { ZError, SyncLocker, BaseController, router } from 'zutils' +import { ZError, BaseController, router } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' import { FUSION_CFG } from 'configs/fusion' import { ALL_ITEMS } from 'configs/items' import { LOTTERY_CFG } from 'configs/lottery' @@ -80,7 +81,7 @@ export default class LotteryController extends BaseController { // @router('get /api/lottery/draw') async draw(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) let user = req.user const { start, end, rewards } = LOTTERY_CFG const startTime = new Date(start).getTime() @@ -144,7 +145,7 @@ export default class LotteryController extends BaseController { // @router('get /api/lottery/fusion') async fusion(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) let user = req.user let items = await ActivityItem.find({ user: user.id, activity: user.activity }) let itemCountMap = new Map() diff --git a/src/controllers/nft.controller.ts b/src/controllers/nft.controller.ts index 1465e90..9ed35ae 100644 --- a/src/controllers/nft.controller.ts +++ b/src/controllers/nft.controller.ts @@ -5,7 +5,8 @@ import { NFTHolderRecord } from 'models/NFTHodlerRecord' import { queryNftBalance } from 'services/chain.svr' import { generateChestLevel, generateNewChest } from 'services/game.svr' import { checkDiscordRole } from 'services/oauth.svr' -import { SyncLocker, BaseController, router, role, ROLE_ANON, ZError } from 'zutils' +import { BaseController, router, role, ROLE_ANON, ZError } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' const sourceList = require('../../configs/partner_nft_list.json') const tierMap = { @@ -103,7 +104,7 @@ class NftController extends BaseController { */ // @router('post /api/partner/claim') async claimNftHolderReward(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) const user = req.user let { contract } = req.params if (!contract) { @@ -142,7 +143,7 @@ class NftController extends BaseController { @router('post /api/partner/claim') async claimNftHolderRewardDC(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) // throw new ZError(20, 'maintance now, please try later') logger.db('claim_partner', req) diff --git a/src/controllers/sign.controller.ts b/src/controllers/sign.controller.ts index 299db0a..abf560d 100644 --- a/src/controllers/sign.controller.ts +++ b/src/controllers/sign.controller.ts @@ -1,4 +1,5 @@ -import { ZError, SyncLocker, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils' +import { ZError, ZRedisClient, BaseController, ROLE_ANON, role, router } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' import { BOOST_CFG } from 'configs/boost' import { ActivityUser } from 'models/ActivityUser' import { DEFAULT_EXPIRED, NonceRecord } from 'models/NonceRecord' @@ -163,7 +164,7 @@ class SignController extends BaseController { // @router('get /api/user/state/boost') async boost(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) const user = req.user if (user.boost > 1 && user.boostExpire && user.boostExpire > Date.now()) { throw new ZError(11, 'already boosted') diff --git a/src/controllers/tasks.controller.ts b/src/controllers/tasks.controller.ts index 0c38180..a1f09ed 100644 --- a/src/controllers/tasks.controller.ts +++ b/src/controllers/tasks.controller.ts @@ -1,4 +1,5 @@ -import { ZError, SyncLocker, BaseController, router } from 'zutils' +import { ZError, BaseController, router } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' import { TaskCfg, TaskTypeEnum } from 'models/ActivityInfo' import { TaskStatus, TaskStatusEnum } from 'models/ActivityUser' import { join } from 'path' @@ -77,7 +78,7 @@ export default class TasksController extends BaseController { @router('post /api/tasks/begin_task') async beginTask(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('begin_task', req) let user = req.user let activity = req.activity @@ -175,7 +176,7 @@ export default class TasksController extends BaseController { @router('post /api/tasks/claim') async claimTask(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('claim_task', req) const user = req.user const activity = req.activity diff --git a/src/controllers/voucher.controller.ts b/src/controllers/voucher.controller.ts index b130374..8b9387e 100644 --- a/src/controllers/voucher.controller.ts +++ b/src/controllers/voucher.controller.ts @@ -3,7 +3,8 @@ import logger from 'logger/logger' import { ChestStatusEnum } from 'models/ActivityChest' import { SourceEnum, VoucherRecord, VoucherStatusEnum } from 'models/VoucherRecord' import { generateNewChest } from 'services/game.svr' -import { SyncLocker, BaseController, router, ZError } from 'zutils' +import { BaseController, router, ZError } from 'zutils' +import { SyncLocker } from 'common/SyncLocker' import { customAlphabet } from 'nanoid' import { BASE52_ALPHABET } from 'common/Constants' /** @@ -90,7 +91,7 @@ class VoucherController extends BaseController { @router('post /api/voucher/claim') async claimVoucherReward(req) { - new SyncLocker().checkLock(req) + await new SyncLocker().checkLock(req) logger.db('claim_voucher', req) const user = req.user const { id } = req.params diff --git a/yarn.lock b/yarn.lock index b38715e..9a0c950 100644 --- a/yarn.lock +++ b/yarn.lock @@ -815,6 +815,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -2231,6 +2236,11 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -2507,6 +2517,11 @@ denque@^1.5.0: resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -3801,6 +3816,21 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ioredis@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.1.tgz#1c56b70b759f01465913887375ed809134296f40" + integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" @@ -4508,6 +4538,16 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + lodash.isfunction@^3.0.6: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" @@ -4908,6 +4948,11 @@ next-tick@^1.1.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== +node-abort-controller@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + node-addon-api@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" @@ -5427,6 +5472,13 @@ redis@^3.1.2: redis-errors "^1.2.0" redis-parser "^3.0.0" +redlock@^5.0.0-beta.2: + version "5.0.0-beta.2" + resolved "https://registry.yarnpkg.com/redlock/-/redlock-5.0.0-beta.2.tgz#a629c07e07d001c0fdd9f2efa614144c4416fe44" + integrity sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw== + dependencies: + node-abort-controller "^3.0.1" + reflect-metadata@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.1.tgz#8d5513c0f5ef2b4b9c3865287f3c0940c1f67f74" @@ -5868,6 +5920,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"