diff --git a/.eslintrc.js b/.eslintrc.js index 3b6a7546..6a3ba8f9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -181,6 +181,10 @@ module.exports = { ], globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly' }, parser: '@typescript-eslint/parser', + parserOptions: { + project: true, + tsconfigRootDir: __dirname, + }, plugins: ['@typescript-eslint'], rules: { // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules diff --git a/package.json b/package.json index baed19ac..26509876 100644 --- a/package.json +++ b/package.json @@ -27,15 +27,15 @@ "@types/node": "18.16.0", "@types/node-forge": "1.3.2", "@types/ws": "8.5.4", - "@typescript-eslint/eslint-plugin": "5.59.8", - "@typescript-eslint/parser": "5.59.8", + "@typescript-eslint/eslint-plugin": "6.4.0", + "@typescript-eslint/parser": "6.4.0", "chai": "4.3.7", "dotenv-flow": "3.2.0", - "eslint": "8.42.0", + "eslint": "8.47.0", "eslint-config-prettier": "8.8.0", - "eslint-import-resolver-typescript": "3.5.5", + "eslint-import-resolver-typescript": "3.6.0", "eslint-plugin-ascii": "1.0.0", - "eslint-plugin-import": "2.27.5", + "eslint-plugin-import": "2.28.0", "eslint-plugin-simple-import-sort": "10.0.0", "glob": "10.2.6", "husky": "^8.0.3", diff --git a/packages/client/scripts/generate-client.js b/packages/client/scripts/generate-client.js index 1feb9a6f..a51af6bb 100644 --- a/packages/client/scripts/generate-client.js +++ b/packages/client/scripts/generate-client.js @@ -64,8 +64,11 @@ async function addSingleMethod(state, fileName) { if ( !stmt.importClause.namedBindings || - stmt.importClause.namedBindings.kind !== ts.SyntaxKind.NamedImports - ) { throwError(stmt, fileName, 'Only named imports are supported!') } + stmt.importClause.namedBindings.kind !== + ts.SyntaxKind.NamedImports + ) { + throwError(stmt, fileName, 'Only named imports are supported!') + } let module = stmt.moduleSpecifier.text @@ -131,11 +134,7 @@ async function addSingleMethod(state, fileName) { })() if (!isExported && !isPrivate) { - throwError( - stmt, - fileName, - 'Public methods MUST be exported.', - ) + throwError(stmt, fileName, 'Public methods MUST be exported.') } if (isExported && !checkForFlag(stmt, '@internal')) { @@ -182,16 +181,20 @@ async function addSingleMethod(state, fileName) { ) } - const returnsExported = (stmt.body ? - ts.getLeadingCommentRanges(fileFullText, stmt.body.pos + 2) || - (stmt.statements && - stmt.statements.length && - ts.getLeadingCommentRanges( - fileFullText, - stmt.statements[0].pos, - )) || - [] : - [] + const returnsExported = ( + stmt.body ? + ts.getLeadingCommentRanges( + fileFullText, + stmt.body.pos + 2, + ) || + (stmt.statements && + stmt.statements.length && + ts.getLeadingCommentRanges( + fileFullText, + stmt.statements[0].pos, + )) || + [] : + [] ) .map((range) => fileFullText.substring(range.pos, range.end)) .join('\n') @@ -275,7 +278,9 @@ async function addSingleMethod(state, fileName) { } async function main() { - const output = fs.createWriteStream(path.join(__dirname, '../src/client.ts')) + const output = fs.createWriteStream( + path.join(__dirname, '../src/client.ts'), + ) const state = { imports: {}, fields: [], @@ -295,7 +300,8 @@ async function main() { } output.write( - '/* THIS FILE WAS AUTO-GENERATED */\n' + + '/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging, @typescript-eslint/unified-signatures */\n' + + '/* THIS FILE WAS AUTO-GENERATED */\n' + "import { BaseTelegramClient, BaseTelegramClientOptions } from '@mtcute/core'\n" + "import { tl } from '@mtcute/tl'\n", ) @@ -336,7 +342,9 @@ async function main() { * @param name Event name * @param handler ${updates.toSentence(type, 'full')} */ -on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this\n`) +on(name: '${type.typeName}', handler: ((upd: ${ + type.updateType +}) => void)): this\n`) }) const printer = ts.createPrinter() @@ -406,7 +414,9 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this it.initializer = undefined const deleteParents = (obj) => { - if (Array.isArray(obj)) { return obj.forEach((it) => deleteParents(it)) } + if (Array.isArray(obj)) { + return obj.forEach((it) => deleteParents(it)) + } if (obj.parent) delete obj.parent @@ -455,7 +465,7 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this for (const name of [origName, ...aliases]) { if (!hasOverloads) { if (!comment.match(/\/\*\*?\s*\*\//)) { - // empty comment, no need to write it + // empty comment, no need to write it output.write(comment + '\n') } @@ -465,18 +475,14 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this } if (!overload) { - classContents.push( - `${name} = ${origName}`, - ) + classContents.push(`${name} = ${origName}`) } } }, ) output.write('}\n') - output.write( - '\nexport class TelegramClient extends BaseTelegramClient {\n', - ) + output.write('\nexport class TelegramClient extends BaseTelegramClient {\n') state.fields.forEach(({ code }) => output.write(`protected ${code}\n`)) @@ -501,10 +507,9 @@ on(name: '${type.typeName}', handler: ((upd: ${type.updateType}) => void)): this await fs.promises.writeFile(targetFile, fullSource) // fix using eslint - require('child_process').execSync( - `pnpm exec eslint --fix ${targetFile}`, - { stdio: 'inherit' }, - ) + require('child_process').execSync(`pnpm exec eslint --fix ${targetFile}`, { + stdio: 'inherit', + }) } main().catch(console.error) diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index 6f8fd260..e127382f 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */ /* THIS FILE WAS AUTO-GENERATED */ import { Readable } from 'stream' @@ -8,7 +9,6 @@ import { Deque, MaybeArray, MaybeAsync, - SessionConnection, SortedLinkedList, } from '@mtcute/core' import { ConditionVariable } from '@mtcute/core/src/utils/condition-variable' @@ -1908,14 +1908,15 @@ export interface TelegramClient extends BaseTelegramClient { /** * Total file size. Automatically inferred for Buffer, File and local files. - * - * When using with streams, if `fileSize` is not passed, the entire file is - * first loaded into memory to determine file size, and used as a Buffer later. - * This might be a major performance bottleneck, so be sure to provide file size - * when using streams and file size is known (which often is the case). */ fileSize?: number + /** + * If the file size is unknown, you can provide an estimate, + * which will be used to determine appropriate part size. + */ + estimatedSize?: number + /** * File MIME type. By default is automatically inferred from magic number * If MIME can't be inferred, it defaults to `application/octet-stream` @@ -1930,11 +1931,16 @@ export interface TelegramClient extends BaseTelegramClient { */ partSize?: number + /** + * Number of parts to be sent in parallel per connection. + */ + requestsPerConnection?: number + /** * Function that will be called after some part has been uploaded. * * @param uploaded Number of bytes already uploaded - * @param total Total file size + * @param total Total file size, if known */ progressCallback?: (uploaded: number, total: number) => void }): Promise @@ -4019,7 +4025,6 @@ export class TelegramClient extends BaseTelegramClient { protected _selfUsername: string | null protected _pendingConversations: Record protected _hasConversations: boolean - protected _downloadConnections: Record protected _parseModes: Record protected _defaultParseMode: string | null protected _updatesLoopActive: boolean @@ -4054,7 +4059,6 @@ export class TelegramClient extends BaseTelegramClient { this.log.prefix = '[USER N/A] ' this._pendingConversations = {} this._hasConversations = false - this._downloadConnections = {} this._parseModes = {} this._defaultParseMode = null this._updatesLoopActive = false diff --git a/packages/client/src/methods/_imports.ts b/packages/client/src/methods/_imports.ts index a67ffb0b..0b2abc89 100644 --- a/packages/client/src/methods/_imports.ts +++ b/packages/client/src/methods/_imports.ts @@ -2,12 +2,7 @@ import { Readable } from 'stream' // @copy -import { - AsyncLock, - MaybeArray, - MaybeAsync, - SessionConnection, -} from '@mtcute/core' +import { AsyncLock, MaybeArray, MaybeAsync } from '@mtcute/core' // @copy import { Logger } from '@mtcute/core/src/utils/logger' // @copy diff --git a/packages/client/src/methods/auth/start.ts b/packages/client/src/methods/auth/start.ts index cd7f6327..bef94d44 100644 --- a/packages/client/src/methods/auth/start.ts +++ b/packages/client/src/methods/auth/start.ts @@ -155,8 +155,9 @@ export async function start( me.isBot, ) - // todo where is this._disableUpdates? - if (!false) { + this.network.setIsPremium(me.isPremium) + + if (!this.network.params.disableUpdates) { this._catchUpChannels = Boolean(params.catchUp) if (!params.catchUp) { @@ -176,14 +177,18 @@ export async function start( if (!(e instanceof tl.errors.AuthKeyUnregisteredError)) throw e } - if (!params.phone && !params.botToken) { throw new MtArgumentError('Neither phone nor bot token were provided') } + if (!params.phone && !params.botToken) { + throw new MtArgumentError('Neither phone nor bot token were provided') + } let phone = params.phone ? await resolveMaybeDynamic(params.phone) : null if (phone) { phone = normalizePhoneNumber(phone) - if (!params.code) { throw new MtArgumentError('You must pass `code` to use `phone`') } + if (!params.code) { + throw new MtArgumentError('You must pass `code` to use `phone`') + } } else { const botToken = params.botToken ? await resolveMaybeDynamic(params.botToken) : diff --git a/packages/client/src/methods/files/_initialize.ts b/packages/client/src/methods/files/_initialize.ts deleted file mode 100644 index f01ea4b1..00000000 --- a/packages/client/src/methods/files/_initialize.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { SessionConnection } from '@mtcute/core' - -import { TelegramClient } from '../../client' - -// @extension -interface FilesExtension { - _downloadConnections: Record -} - -// @initialize -function _initializeFiles(this: TelegramClient): void { - this._downloadConnections = {} -} diff --git a/packages/client/src/methods/files/download-iterable.ts b/packages/client/src/methods/files/download-iterable.ts index 1177ce76..3ff2b5a7 100644 --- a/packages/client/src/methods/files/download-iterable.ts +++ b/packages/client/src/methods/files/download-iterable.ts @@ -1,3 +1,4 @@ +import { ConditionVariable, ConnectionKind } from '@mtcute/core' import { fileIdToInputFileLocation, fileIdToInputWebFileLocation, @@ -10,9 +11,16 @@ import { FileDownloadParameters, FileLocation, MtArgumentError, + MtUnsupportedError, } from '../../types' import { determinePartSize } from '../../utils/file-utils' +// small files (less than 128 kb) are downloaded using the "downloadSmall" pool +// furthermore, if the file is small and is located on our main DC, it will be downloaded +// using the current main connection +const SMALL_FILE_MAX_SIZE = 131072 +const REQUESTS_PER_CONNECTION = 3 // some arbitrary magic value that seems to work best + /** * Download a file and return it as an iterable, which yields file contents * in chunks of a given size. Order of the chunks is guaranteed to be @@ -25,16 +33,6 @@ export async function* downloadAsIterable( this: TelegramClient, params: FileDownloadParameters, ): AsyncIterableIterator { - const partSizeKb = - params.partSize ?? - (params.fileSize ? determinePartSize(params.fileSize) : 64) - - if (partSizeKb % 4 !== 0) { - throw new MtArgumentError( - `Invalid part size: ${partSizeKb}. Must be divisible by 4.`, - ) - } - const offset = params.offset ?? 0 if (offset % 4096 !== 0) { @@ -77,91 +75,151 @@ export async function* downloadAsIterable( // we will receive a FileMigrateError in case this is invalid if (!dcId) dcId = this._defaultDc.id + const partSizeKb = + params.partSize ?? (fileSize ? determinePartSize(fileSize) : 64) + + if (partSizeKb % 4 !== 0) { + throw new MtArgumentError( + `Invalid part size: ${partSizeKb}. Must be divisible by 4.`, + ) + } + const chunkSize = partSizeKb * 1024 - const limit = - params.limit ?? - // derive limit from chunk size, file size and offset - (fileSize ? - ~~((fileSize + chunkSize - offset - 1) / chunkSize) : - // we will receive an error when we have reached the end anyway - Infinity) + let limitBytes = params.limit ?? fileSize ?? Infinity + if (limitBytes === 0) return - // fixme - throw new Error('TODO') + let numChunks = + limitBytes === Infinity ? + Infinity : + ~~((limitBytes + chunkSize - offset - 1) / chunkSize) - // let connection = this._downloadConnections[dcId] + let nextChunkIdx = 0 + let nextWorkerChunkIdx = 0 + const nextChunkCv = new ConditionVariable() + const buffer: Record = {} - // if (!connection) { - // connection = await this.createAdditionalConnection(dcId) - // this._downloadConnections[dcId] = connection - // } - // - // const requestCurrent = async (): Promise => { - // let result: - // | tl.RpcCallReturn['upload.getFile'] - // | tl.RpcCallReturn['upload.getWebFile'] - // - // try { - // result = await this.call( - // { - // _: isWeb ? 'upload.getWebFile' : 'upload.getFile', - // // eslint-disable-next-line @typescript-eslint/no-explicit-any - // location: location as any, - // offset, - // limit: chunkSize, - // }, - // { connection }, - // ) - // // eslint-disable-next-line @typescript-eslint/no-explicit-any - // } catch (e: any) { - // if (e.constructor === tl.errors.FileMigrateXError) { - // connection = this._downloadConnections[e.new_dc] - // - // if (!connection) { - // connection = await this.createAdditionalConnection(e.new_dc) - // this._downloadConnections[e.new_dc] = connection - // } - // - // return requestCurrent() - // } else if (e.constructor === tl.errors.FilerefUpgradeNeededError) { - // // todo: implement someday - // // see: https://github.com/LonamiWebs/Telethon/blob/0e8bd8248cc649637b7c392616887c50986427a0/telethon/client/downloads.py#L99 - // throw new MtUnsupportedError('File ref expired!') - // } else throw e - // } - // - // if (result._ === 'upload.fileCdnRedirect') { - // // we shouldnt receive them since cdnSupported is not set in the getFile request. - // // also, i couldnt find any media that would be downloaded from cdn, so even if - // // i implemented that, i wouldnt be able to test that, so :shrug: - // throw new MtUnsupportedError( - // 'Received CDN redirect, which is not supported (yet)', - // ) - // } - // - // if ( - // result._ === 'upload.webFile' && - // result.size && - // limit === Infinity - // ) { - // limit = result.size - // } - // - // return result.bytes - // } - // - // for (let i = 0; i < limit; i++) { - // const buf = await requestCurrent() - // - // if (buf.length === 0) { - // // we've reached the end - // return - // } - // - // yield buf - // offset += chunkSize - // - // params.progressCallback?.(offset, limit) - // } + const isSmall = fileSize && fileSize <= SMALL_FILE_MAX_SIZE + let connectionKind: ConnectionKind + + if (isSmall) { + connectionKind = + dcId === this.network.getPrimaryDcId() ? 'main' : 'downloadSmall' + } else { + connectionKind = 'download' + } + const poolSize = this.network.getPoolSize(connectionKind, dcId) + + this.log.debug( + 'Downloading file of size %d from dc %d using %s connection pool (pool size: %d)', + limitBytes, + dcId, + connectionKind, + poolSize, + ) + + const downloadChunk = async ( + chunk = nextWorkerChunkIdx++, + ): Promise => { + let result: + | tl.RpcCallReturn['upload.getFile'] + | tl.RpcCallReturn['upload.getWebFile'] + + try { + result = await this.call( + { + _: isWeb ? 'upload.getWebFile' : 'upload.getFile', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + location: location as any, + offset: chunkSize * chunk, + limit: chunkSize, + }, + { dcId, kind: connectionKind }, + ) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + if (e.constructor === tl.errors.FileMigrateXError) { + dcId = e.new_dc + + return downloadChunk(chunk) + } else if (e.constructor === tl.errors.FilerefUpgradeNeededError) { + // todo: implement someday + // see: https://github.com/LonamiWebs/Telethon/blob/0e8bd8248cc649637b7c392616887c50986427a0/telethon/client/downloads.py#L99 + throw new MtUnsupportedError('File ref expired!') + } else throw e + } + + if (result._ === 'upload.fileCdnRedirect') { + // we shouldnt receive them since cdnSupported is not set in the getFile request. + // also, i couldnt find any media that would be downloaded from cdn, so even if + // i implemented that, i wouldnt be able to test that, so :shrug: + throw new MtUnsupportedError( + 'Received CDN redirect, which is not supported (yet)', + ) + } + + if ( + result._ === 'upload.webFile' && + result.size && + limitBytes === Infinity + ) { + limitBytes = result.size + numChunks = ~~((limitBytes + chunkSize - offset - 1) / chunkSize) + } + + buffer[chunk] = result.bytes + + if (chunk === nextChunkIdx) { + nextChunkCv.notify() + } + + if ( + nextWorkerChunkIdx < numChunks && + result.bytes.length === chunkSize + ) { + return downloadChunk() + } + } + + let error: unknown = undefined + Promise.all( + Array.from( + { length: Math.min(poolSize * REQUESTS_PER_CONNECTION, numChunks) }, + downloadChunk, + ), + ) + .catch((e) => { + this.log.debug('download workers errored: %s', e.message) + error = e + nextChunkCv.notify() + }) + .then(() => { + this.log.debug('download workers finished') + }) + + let position = offset + + while (position < limitBytes) { + await nextChunkCv.wait() + + if (error) throw error + + while (nextChunkIdx in buffer) { + const buf = buffer[nextChunkIdx] + delete buffer[nextChunkIdx] + + position += buf.length + + params.progressCallback?.(position, limitBytes) + + yield buf + + nextChunkIdx++ + + if (buf.length < chunkSize) { + // we received the last chunk + return + } + } + } } diff --git a/packages/client/src/methods/files/upload-file.ts b/packages/client/src/methods/files/upload-file.ts index 02a346fc..c969cc3b 100644 --- a/packages/client/src/methods/files/upload-file.ts +++ b/packages/client/src/methods/files/upload-file.ts @@ -3,7 +3,7 @@ import { fromBuffer as fileTypeFromBuffer } from 'file-type' import type { ReadStream } from 'fs' import { Readable } from 'stream' -import { randomLong } from '@mtcute/core' +import { AsyncLock, randomLong } from '@mtcute/core' import { tl } from '@mtcute/tl' import { TelegramClient } from '../../client' @@ -13,7 +13,6 @@ import { bufferToStream, convertWebStreamToNodeReadable, readBytesFromStream, - readStreamUntilEnd, } from '../../utils/stream-utils' let fs: any = null @@ -29,6 +28,14 @@ const OVERRIDE_MIME: Record = { 'audio/opus': 'audio/ogg', } +// small files (less than 128 kb) are uploaded using the current connection and not the "upload" pool +const SMALL_FILE_MAX_SIZE = 131072 +const BIG_FILE_MIN_SIZE = 10485760 // files >10 MB are considered "big" +const DEFAULT_FILE_NAME = 'unnamed' +const REQUESTS_PER_CONNECTION = 3 +const MAX_PART_COUNT = 4000 // 512 kb * 4000 = 2000 MiB +const MAX_PART_COUNT_PREMIUM = 8000 // 512 kb * 8000 = 4000 MiB + /** * Upload a file to Telegram servers, without actually * sending a message anywhere. Useful when an `InputFile` is required. @@ -60,14 +67,15 @@ export async function uploadFile( /** * Total file size. Automatically inferred for Buffer, File and local files. - * - * When using with streams, if `fileSize` is not passed, the entire file is - * first loaded into memory to determine file size, and used as a Buffer later. - * This might be a major performance bottleneck, so be sure to provide file size - * when using streams and file size is known (which often is the case). */ fileSize?: number + /** + * If the file size is unknown, you can provide an estimate, + * which will be used to determine appropriate part size. + */ + estimatedSize?: number + /** * File MIME type. By default is automatically inferred from magic number * If MIME can't be inferred, it defaults to `application/octet-stream` @@ -82,11 +90,16 @@ export async function uploadFile( */ partSize?: number + /** + * Number of parts to be sent in parallel per connection. + */ + requestsPerConnection?: number + /** * Function that will be called after some part has been uploaded. * * @param uploaded Number of bytes already uploaded - * @param total Total file size + * @param total Total file size, if known */ progressCallback?: (uploaded: number, total: number) => void }, @@ -94,7 +107,7 @@ export async function uploadFile( // normalize params let file = params.file let fileSize = -1 // unknown - let fileName = 'unnamed' + let fileName = DEFAULT_FILE_NAME let fileMime = params.fileMime if (Buffer.isBuffer(file)) { @@ -162,12 +175,12 @@ export async function uploadFile( } } - if (fileName === 'unnamed') { + if (fileName === DEFAULT_FILE_NAME) { // try to infer from url const url = new URL(file.url) const name = url.pathname.split('/').pop() - if (name && name.indexOf('.') > -1) { + if (name && name.includes('.')) { fileName = name } } @@ -192,42 +205,88 @@ export async function uploadFile( // set file size if not automatically inferred if (fileSize === -1 && params.fileSize) fileSize = params.fileSize - if (fileSize === -1) { - // load the entire stream into memory - const buffer = await readStreamUntilEnd(file as Readable) - fileSize = buffer.length - file = bufferToStream(buffer) + let partSizeKb = params.partSize + + if (!partSizeKb) { + if (fileSize === -1) { + partSizeKb = params.estimatedSize ? + determinePartSize(params.estimatedSize) : + 64 + } else { + partSizeKb = determinePartSize(fileSize) + } } if (!(file instanceof Readable)) { throw new MtArgumentError('Could not convert input `file` to stream!') } - const partSizeKb = params.partSize ?? determinePartSize(fileSize) - if (partSizeKb > 512) { throw new MtArgumentError(`Invalid part size: ${partSizeKb}KB`) } const partSize = partSizeKb * 1024 - const isBig = fileSize > 10485760 // 10 MB - const hash = this._crypto.createMd5() + let partCount = + fileSize === -1 ? -1 : ~~((fileSize + partSize - 1) / partSize) + const maxPartCount = this.network.params.isPremium ? + MAX_PART_COUNT_PREMIUM : + MAX_PART_COUNT + + if (partCount > maxPartCount) { + throw new MtArgumentError( + `File is too large (max ${maxPartCount} parts, got ${partCount})`, + ) + } + + const isBig = fileSize === -1 || fileSize > BIG_FILE_MIN_SIZE + const isSmall = fileSize !== -1 && fileSize < SMALL_FILE_MAX_SIZE + const connectionKind = isSmall ? 'main' : 'upload' + const connectionPoolSize = Math.min( + this.network.getPoolSize(connectionKind), + partCount, + ) + const requestsPerConnection = + params.requestsPerConnection ?? REQUESTS_PER_CONNECTION - const partCount = ~~((fileSize + partSize - 1) / partSize) this.log.debug( - 'uploading %d bytes file in %d chunks, each %d bytes', + 'uploading %d bytes file in %d chunks, each %d bytes in %s connection pool of size %d', fileSize, partCount, partSize, + connectionKind, + connectionPoolSize, ) // why is the file id generated by the client? // isn't the server supposed to generate it and handle collisions? const fileId = randomLong() - let pos = 0 + const stream = file - for (let idx = 0; idx < partCount; idx++) { - const part = await readBytesFromStream(file, partSize) + let pos = 0 + let idx = 0 + const lock = new AsyncLock() + + const uploadNextPart = async (): Promise => { + const thisIdx = idx++ + + let part + + try { + await lock.acquire() + part = await readBytesFromStream(stream, partSize) + } finally { + lock.release() + } + + if (fileSize === -1 && stream.readableEnded) { + fileSize = pos + (part?.length ?? 0) + partCount = ~~((fileSize + partSize - 1) / partSize) + this.log.debug( + 'readable ended, file size = %d, part count = %d', + fileSize, + partCount, + ) + } if (!part) { throw new MtArgumentError( @@ -236,15 +295,15 @@ export async function uploadFile( } if (!Buffer.isBuffer(part)) { - throw new MtArgumentError(`Part ${idx} was not a Buffer!`) + throw new MtArgumentError(`Part ${thisIdx} was not a Buffer!`) } if (part.length > partSize) { throw new MtArgumentError( - `Part ${idx} had invalid size (expected ${partSize}, got ${part.length})`, + `Part ${thisIdx} had invalid size (expected ${partSize}, got ${part.length})`, ) } - if (idx === 0 && fileMime === undefined) { + if (thisIdx === 0 && fileMime === undefined) { const fileType = await fileTypeFromBuffer(part) fileMime = fileType?.mime @@ -260,37 +319,43 @@ export async function uploadFile( } } - if (!isBig) { - // why md5 only small files? - // big files have more chance of corruption, but whatever - // also isn't integrity guaranteed by mtproto? - await hash.update(part) - } - - pos += part.length - // why const request = isBig ? ({ _: 'upload.saveBigFilePart', fileId, - filePart: idx, + filePart: thisIdx, fileTotalParts: partCount, bytes: part, - } as tl.upload.RawSaveBigFilePartRequest) : + } satisfies tl.upload.RawSaveBigFilePartRequest) : ({ _: 'upload.saveFilePart', fileId, - filePart: idx, + filePart: thisIdx, bytes: part, - } as tl.upload.RawSaveFilePartRequest) + } satisfies tl.upload.RawSaveFilePartRequest) - const result = await this.call(request) + const result = await this.call(request, { kind: connectionKind }) if (!result) throw new Error(`Failed to upload part ${idx}`) + pos += part.length + params.progressCallback?.(pos, fileSize) + + if (idx === partCount) return + + return uploadNextPart() } + await Promise.all( + Array.from( + { + length: connectionPoolSize * requestsPerConnection, + }, + uploadNextPart, + ), + ) + let inputFile: tl.TypeInputFile if (isBig) { @@ -306,7 +371,7 @@ export async function uploadFile( id: fileId, parts: partCount, name: fileName, - md5Checksum: (await hash.digest()).toString('hex'), + md5Checksum: '', // tdlib doesn't do this, why should we? } } diff --git a/packages/client/src/methods/updates.ts b/packages/client/src/methods/updates.ts index de140525..46da85a8 100644 --- a/packages/client/src/methods/updates.ts +++ b/packages/client/src/methods/updates.ts @@ -558,11 +558,15 @@ async function _fetchPeersForShort( if ( msg.replyTo._ === 'messageReplyHeader' && !(await fetchPeer(msg.replyTo.replyToPeerId)) - ) { return null } + ) { + return null + } if ( msg.replyTo._ === 'messageReplyStoryHeader' && !(await fetchPeer(msg.replyTo.userId)) - ) { return null } + ) { + return null + } } if (msg._ !== 'messageService') { @@ -791,7 +795,7 @@ async function _fetchChannelDifference( if (!_pts) _pts = fallbackPts if (!_pts) { - this._updsLog.warn( + this._updsLog.debug( 'fetchChannelDifference failed for channel %d: base pts not available', channelId, ) diff --git a/packages/client/src/types/conversation.ts b/packages/client/src/types/conversation.ts index f774a6a0..a3f5d9bd 100644 --- a/packages/client/src/types/conversation.ts +++ b/packages/client/src/types/conversation.ts @@ -133,8 +133,7 @@ export class Conversation { const pending = this.client['_pendingConversations'] - const idx = - pending[this._chatId].indexOf(this) + const idx = pending[this._chatId].indexOf(this) if (idx > -1) { // just in case @@ -143,8 +142,7 @@ export class Conversation { if (!pending[this._chatId].length) { delete pending[this._chatId] } - this.client['_hasConversations'] = - Object.keys(pending).length > 0 + this.client['_hasConversations'] = Object.keys(pending).length > 0 // reset pending status this._queuedNewMessage.clear() @@ -279,6 +277,7 @@ export class Conversation { if (timeout !== null) { timer = setTimeout(() => { + console.log('timed out') promise.reject(new tl.errors.TimeoutError()) this._queuedNewMessage.removeBy((it) => it.promise === promise) }, timeout) @@ -537,7 +536,9 @@ export class Conversation { it.promise.resolve(msg) delete this._pendingEditMessage[msg.id] } - })().catch((e) => this.client['_emitError'](e)) + })().catch((e) => { + this.client['_emitError'](e) + }) } private _onHistoryRead(upd: HistoryReadUpdate) { diff --git a/packages/client/src/types/files/file-location.ts b/packages/client/src/types/files/file-location.ts index c52f21b0..261d4971 100644 --- a/packages/client/src/types/files/file-location.ts +++ b/packages/client/src/types/files/file-location.ts @@ -4,6 +4,7 @@ import { tl } from '@mtcute/tl' import { TelegramClient } from '../../client' import { makeInspectable } from '../utils' +import { FileDownloadParameters } from './utils' /** * Information about file location. @@ -50,48 +51,61 @@ export class FileLocation { * in chunks of a given size. Order of the chunks is guaranteed to be * consecutive. * - * Shorthand for `client.downloadAsIterable({ location: this })` - * + * @param params Download parameters * @link TelegramClient.downloadAsIterable */ - downloadIterable(): AsyncIterableIterator { - return this.client.downloadAsIterable({ location: this }) + downloadIterable( + params?: Partial, + ): AsyncIterableIterator { + return this.client.downloadAsIterable({ + ...params, + location: this, + }) } /** * Download a file and return it as a Node readable stream, * streaming file contents. * - * Shorthand for `client.downloadAsStream({ location: this })` - * * @link TelegramClient.downloadAsStream */ - downloadStream(): Readable { - return this.client.downloadAsStream({ location: this }) + downloadStream(params?: Partial): Readable { + return this.client.downloadAsStream({ + ...params, + location: this, + }) } /** * Download a file and return its contents as a Buffer. * - * Shorthand for `client.downloadAsBuffer({ location: this })` - * + * @param params File download parameters * @link TelegramClient.downloadAsBuffer */ - downloadBuffer(): Promise { - return this.client.downloadAsBuffer({ location: this }) + downloadBuffer(params?: Partial): Promise { + return this.client.downloadAsBuffer({ + ...params, + location: this, + }) } /** * Download a remote file to a local file (only for NodeJS). * Promise will resolve once the download is complete. * - * Shorthand for `client.downloadToFile(filename, { location: this })` - * * @param filename Local file name + * @param params File download parameters * @link TelegramClient.downloadToFile */ - downloadToFile(filename: string): Promise { - return this.client.downloadToFile(filename, { location: this }) + downloadToFile( + filename: string, + params?: Partial, + ): Promise { + return this.client.downloadToFile(filename, { + ...params, + location: this, + fileSize: this.fileSize, + }) } } diff --git a/packages/client/src/types/files/utils.ts b/packages/client/src/types/files/utils.ts index 4132f15d..bd66a1be 100644 --- a/packages/client/src/types/files/utils.ts +++ b/packages/client/src/types/files/utils.ts @@ -97,7 +97,7 @@ export interface FileDownloadParameters { offset?: number /** - * Number of chunks (!) of that given size that will be downloaded. + * Number of bytes to be downloaded. * By default, downloads the entire file */ limit?: number diff --git a/packages/client/src/utils/file-utils.ts b/packages/client/src/utils/file-utils.ts index 94d8f407..f5a8d809 100644 --- a/packages/client/src/utils/file-utils.ts +++ b/packages/client/src/utils/file-utils.ts @@ -5,10 +5,9 @@ import { MtArgumentError } from '../types' * for upload/download operations. */ export function determinePartSize(fileSize: number): number { - if (fileSize <= 104857600) return 128 // 100 MB + if (fileSize <= 262078465) return 128 // 200 MB if (fileSize <= 786432000) return 256 // 750 MB if (fileSize <= 2097152000) return 512 // 2000 MB - if (fileSize <= 4194304000) return 1024 // 4000 MB throw new MtArgumentError('File is too large') } diff --git a/packages/client/src/utils/stream-utils.ts b/packages/client/src/utils/stream-utils.ts index cdfa020c..db33033c 100644 --- a/packages/client/src/utils/stream-utils.ts +++ b/packages/client/src/utils/stream-utils.ts @@ -35,7 +35,9 @@ class NodeReadable extends Readable { return } if (this.push(res.value)) { - return doRead() + doRead() + + return } this._reading = false this._reader.releaseLock() @@ -49,7 +51,9 @@ class NodeReadable extends Readable { const promise = new Promise((resolve) => { this._doneReading = resolve }) - promise.then(() => this._handleDestroy(err, callback)) + promise.then(() => { + this._handleDestroy(err, callback) + }) } else { this._handleDestroy(err, callback) } @@ -71,26 +75,6 @@ export function convertWebStreamToNodeReadable( return new NodeReadable(webStream, opts) } -export async function readStreamUntilEnd(stream: Readable): Promise { - const chunks = [] - let length = 0 - - while (stream.readable) { - const c = await stream.read() - if (c === null) break - - length += c.length - - if (length > 2097152000) { - throw new Error('File is too big') - } - - chunks.push(c) - } - - return Buffer.concat(chunks) -} - export function bufferToStream(buf: Buffer): Readable { return new Readable({ read() { @@ -109,15 +93,17 @@ export async function readBytesFromStream( let res = stream.read(size) if (!res) { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { stream.on('readable', function handler() { res = stream.read(size) if (res) { stream.off('readable', handler) + stream.off('error', reject) resolve(res) } }) + stream.on('error', reject) }) } diff --git a/packages/client/tests/stream-utils.spec.ts b/packages/client/tests/stream-utils.spec.ts deleted file mode 100644 index 51f10393..00000000 --- a/packages/client/tests/stream-utils.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { expect } from 'chai' -import { describe, it } from 'mocha' -import { Readable } from 'stream' - -import { readStreamUntilEnd } from '../src/utils/stream-utils' - -describe('readStreamUntilEnd', () => { - it('should read stream until end', async () => { - const stream = new Readable({ - read() { - this.push(Buffer.from('aaeeff', 'hex')) - this.push(Buffer.from('ff33ee', 'hex')) - this.push(null) - }, - }) - - expect((await readStreamUntilEnd(stream)).toString('hex')).eq( - 'aaeeffff33ee', - ) - }) -}) diff --git a/packages/core/src/base-client.ts b/packages/core/src/base-client.ts index 48b9d1d2..68f002cd 100644 --- a/packages/core/src/base-client.ts +++ b/packages/core/src/base-client.ts @@ -245,7 +245,7 @@ export class BaseTelegramClient extends EventEmitter { // eslint-disable-next-line @typescript-eslint/no-unused-vars protected _handleUpdate(update: tl.TypeUpdates): void {} - readonly log = new LogManager() + readonly log = new LogManager('client') readonly network: NetworkManager constructor(opts: BaseTelegramClientOptions) { @@ -451,7 +451,6 @@ export class BaseTelegramClient extends EventEmitter { * @param factory New transport factory */ changeTransport(factory: TransportFactory): void { - // todo this.network.changeTransport(factory) } diff --git a/packages/core/src/network/index.ts b/packages/core/src/network/index.ts index 34bac818..b066008a 100644 --- a/packages/core/src/network/index.ts +++ b/packages/core/src/network/index.ts @@ -1,4 +1,8 @@ -export { NetworkManagerExtraParams, RpcCallOptions } from './network-manager' +export { + ConnectionKind, + NetworkManagerExtraParams, + RpcCallOptions, +} from './network-manager' export * from './reconnection' export * from './session-connection' export * from './transports' diff --git a/packages/core/src/network/mtproto-session.ts b/packages/core/src/network/mtproto-session.ts index 0bed00ec..1508b97f 100644 --- a/packages/core/src/network/mtproto-session.ts +++ b/packages/core/src/network/mtproto-session.ts @@ -8,7 +8,8 @@ import { TlWriterMap, } from '@mtcute/tl-runtime' -import { ControllablePromise, +import { + ControllablePromise, Deque, getRandomInt, ICryptoProvider, @@ -118,9 +119,18 @@ export class MtprotoSession { pendingMessages = new LongMap() destroySessionIdToMsgId = new LongMap() + lastPingRtt = NaN + lastPingTime = 0 + lastPingMsgId = Long.ZERO + lastSessionCreatedUid = Long.ZERO + initConnectionCalled = false authorizationPending = false + next429Timeout = 1000 + current429Timeout?: NodeJS.Timeout + next429ResetTimeout?: NodeJS.Timeout + constructor( readonly _crypto: ICryptoProvider, readonly log: Logger, @@ -130,6 +140,15 @@ export class MtprotoSession { this.log.prefix = `[SESSION ${this._sessionId.toString(16)}] ` } + get hasPendingMessages(): boolean { + return Boolean( + this.queuedRpc.length || + this.queuedAcks.length || + this.queuedStateReq.length || + this.queuedResendReq.length, + ) + } + /** * Reset session by resetting auth key(s) and session state */ @@ -140,7 +159,9 @@ export class MtprotoSession { this._authKeyTempSecondary.reset() } + clearTimeout(this.current429Timeout) this.resetState() + this.resetLastPing(true) } /** @@ -221,11 +242,11 @@ export class MtprotoSession { } getSeqNo(isContentRelated = true): number { - let seqNo = this._seqNo * 2 + let seqNo = this._seqNo if (isContentRelated) { seqNo += 1 - this._seqNo += 1 + this._seqNo += 2 } return seqNo @@ -293,4 +314,43 @@ export class MtprotoSession { return messageId } + + onTransportFlood(callback: () => void) { + if (this.current429Timeout) return // already waiting + + // all active queries must be resent after a timeout + this.resetLastPing(true) + + const timeout = this.next429Timeout + + this.next429Timeout = Math.min(this.next429Timeout * 2, 32000) + clearTimeout(this.current429Timeout) + clearTimeout(this.next429ResetTimeout) + + this.current429Timeout = setTimeout(() => { + this.current429Timeout = undefined + callback() + }, timeout) + this.next429ResetTimeout = setTimeout(() => { + this.next429ResetTimeout = undefined + this.next429Timeout = 1000 + }, 60000) + + this.log.debug( + 'transport flood, waiting for %d ms before proceeding', + timeout, + ) + + return Date.now() + timeout + } + + resetLastPing(withTime = false): void { + if (withTime) this.lastPingTime = 0 + + if (!this.lastPingMsgId.isZero()) { + this.pendingMessages.delete(this.lastPingMsgId) + } + + this.lastPingMsgId = Long.ZERO + } } diff --git a/packages/core/src/network/multi-session-connection.ts b/packages/core/src/network/multi-session-connection.ts index 9730a401..bdd4dd59 100644 --- a/packages/core/src/network/multi-session-connection.ts +++ b/packages/core/src/network/multi-session-connection.ts @@ -32,43 +32,48 @@ export class MultiSessionConnection extends EventEmitter { protected _connections: SessionConnection[] = [] - setCount(count: number, doUpdate = true): void { + setCount(count: number, connect = this.params.isMainConnection): void { this._count = count - if (doUpdate) this._updateConnections(true) + this._updateConnections(connect) } private _updateSessions(): void { + // there are two cases + // 1. this msc is main, in which case every connection should have its own session + // 2. this msc is not main, in which case all connections should share the same session + // if (!this.params.isMainConnection) { + // // case 2 + // this._log.debug( + // 'updating sessions count: %d -> 1', + // this._sessions.length, + // ) + // + // if (this._sessions.length === 0) { + // this._sessions.push( + // new MtprotoSession( + // this.params.crypto, + // this._log.create('session'), + // this.params.readerMap, + // this.params.writerMap, + // ), + // ) + // } + // + // // shouldn't happen, but just in case + // while (this._sessions.length > 1) { + // this._sessions.pop()!.reset() + // } + // + // return + // } + this._log.debug( 'updating sessions count: %d -> %d', this._sessions.length, this._count, ) - // there are two cases - // 1. this msc is main, in which case every connection should have its own session - // 2. this msc is not main, in which case all connections should share the same session - if (!this.params.isMainConnection) { - // case 2 - if (this._sessions.length === 0) { - this._sessions.push( - new MtprotoSession( - this.params.crypto, - this._log.create('session'), - this.params.readerMap, - this.params.writerMap, - ), - ) - } - - // shouldn't happen, but just in case - while (this._sessions.length > 1) { - this._sessions.pop()!.reset() - } - - return - } - // case 1 if (this._sessions.length === this._count) return @@ -99,7 +104,7 @@ export class MultiSessionConnection extends EventEmitter { } } - private _updateConnections(active = false): void { + private _updateConnections(connect = false): void { this._updateSessions() if (this._connections.length === this._count) return @@ -141,9 +146,8 @@ export class MultiSessionConnection extends EventEmitter { // create new connections for (let i = this._connections.length; i < this._count; i++) { - const session = this.params.isMainConnection ? - this._sessions[i] : - this._sessions[0] + const session = this._sessions[i] // this.params.isMainConnection ? // : + // this._sessions[0] const conn = new SessionConnection( { ...this.params, @@ -185,9 +189,14 @@ export class MultiSessionConnection extends EventEmitter { }) conn.on('usable', () => this.emit('usable', i)) conn.on('request-auth', () => this.emit('request-auth', i)) + conn.on('flood-done', () => { + this._log.debug('received flood-done from connection %d', i) + + this._connections.forEach((it) => it.flushWhenIdle()) + }) this._connections.push(conn) - if (active) conn.connect() + if (connect) conn.connect() } } @@ -207,31 +216,32 @@ export class MultiSessionConnection extends EventEmitter { stack?: string, timeout?: number, ): Promise { - if (this.params.isMainConnection) { - // find the least loaded connection - let min = Infinity - let minIdx = 0 + // if (this.params.isMainConnection) { + // find the least loaded connection + let min = Infinity + let minIdx = 0 - for (let i = 0; i < this._connections.length; i++) { - const conn = this._connections[i] - const total = - conn._session.queuedRpc.length + - conn._session.pendingMessages.size() + for (let i = 0; i < this._connections.length; i++) { + const conn = this._connections[i] + const total = + conn._session.queuedRpc.length + + conn._session.pendingMessages.size() - if (total < min) { - min = total - minIdx = i - } + if (total < min) { + min = total + minIdx = i } - - return this._connections[minIdx].sendRpc(request, stack, timeout) } + return this._connections[minIdx].sendRpc(request, stack, timeout) + // } + // round-robin connections // since they all share the same session, it doesn't matter which one we use - return this._connections[ - this._nextConnection++ % this._connections.length - ].sendRpc(request, stack, timeout) + // the connection chosen here will only affect the first attempt at sending + // return this._connections[ + // this._nextConnection++ % this._connections.length + // ].sendRpc(request, stack, timeout) } connect(): void { @@ -308,4 +318,8 @@ export class MultiSessionConnection extends EventEmitter { changeTransport(factory: TransportFactory): void { this._connections.forEach((conn) => conn.changeTransport(factory)) } + + getPoolSize(): number { + return this._connections.length + } } diff --git a/packages/core/src/network/network-manager.ts b/packages/core/src/network/network-manager.ts index 233c144c..1053a8d2 100644 --- a/packages/core/src/network/network-manager.ts +++ b/packages/core/src/network/network-manager.ts @@ -2,7 +2,12 @@ import { tl } from '@mtcute/tl' import { TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime' import { ITelegramStorage } from '../storage' -import { ICryptoProvider, Logger, sleep } from '../utils' +import { + createControllablePromise, + ICryptoProvider, + Logger, + sleep, +} from '../utils' import { ConfigManager } from './config-manager' import { MultiSessionConnection } from './multi-session-connection' import { PersistentConnectionParams } from './persistent-connection' @@ -18,6 +23,16 @@ import { defaultTransportFactory, TransportFactory } from './transports' export type ConnectionKind = 'main' | 'upload' | 'download' | 'downloadSmall' +const CLIENT_ERRORS = { + '303': 1, + '400': 1, + '401': 1, + '403': 1, + '404': 1, + '406': 1, + '420': 1, +} + /** * Params passed into {@link NetworkManager} by {@link TelegramClient}. * This type is intended for internal usage only. @@ -62,8 +77,9 @@ const defaultConnectionCountDelegate: ConnectionCountDelegate = ( case 'upload': return isPremium || (dcId !== 2 && dcId !== 4) ? 8 : 4 case 'download': + return 8 // fixme isPremium ? 8 : 2 case 'downloadSmall': - return isPremium ? 8 : 2 + return 2 } } @@ -79,13 +95,14 @@ export interface NetworkManagerExtraParams { usePfs?: boolean /** - * Connection count for each connection kind + * Connection count for each connection kind. + * The function should be pure to avoid unexpected behavior. * * Defaults to TDLib logic: * - main: handled internally, **cannot be changed here** * - upload: if premium or dc id is other than 2 or 4, then 8, otherwise 4 * - download: if premium then 8, otherwise 2 - * - downloadSmall: if premium then 8, otherwise 2 + * - downloadSmall: 2 */ connectionCount?: ConnectionCountDelegate @@ -174,7 +191,24 @@ export class DcConnectionManager { ) download = new MultiSessionConnection( - this.__baseConnectionParams(), + // this.__baseConnectionParams(), + // fixme + { + ...this.__baseConnectionParams(), + dc: { + _: 'dcOption', + ipv6: false, + mediaOnly: true, + tcpoOnly: false, + cdn: false, + static: false, + thisPortOnly: false, + id: 2, + ipAddress: '149.154.167.222', + port: 443, + secret: undefined, + }, + }, this.manager._connectionCount( 'download', this._dc.id, @@ -313,6 +347,10 @@ export class DcConnectionManager { connection.on('request-auth', () => { this.main.requestAuth() }) + + connection.on('error', (err, conn) => { + this.manager.params._emitError(err, conn) + }) } setIsPrimary(isPrimary: boolean): void { @@ -328,6 +366,22 @@ export class DcConnectionManager { } } + setIsPremium(isPremium: boolean): void { + this.upload.setCount( + this.manager._connectionCount('upload', this._dc.id, isPremium), + ) + this.download.setCount( + this.manager._connectionCount('download', this._dc.id, isPremium), + ) + this.downloadSmall.setCount( + this.manager._connectionCount( + 'downloadSmall', + this._dc.id, + isPremium, + ), + ) + } + async loadKeys(): Promise { const permanent = await this.manager._storage.getAuthKeyFor(this.dcId) @@ -471,9 +525,9 @@ export class NetworkManager { Promise.resolve(this._storage.getSelf()).then((self) => { if (self?.isBot) { // bots may receive tmpSessions, which we should respect - this.config - .update(true) - .catch((e) => this.params._emitError(e)) + this.config.update(true).catch((e) => { + this.params._emitError(e) + }) } }) }) @@ -485,35 +539,54 @@ export class NetworkManager { // this._cleanupPrimaryConnection() // ) - dc.main.on('error', (err, conn) => this.params._emitError(err, conn)) - dc.loadKeys() - .catch((e) => this.params._emitError(e)) - .then(() => dc.main.ensureConnected()) + .catch((e) => { + this.params._emitError(e) + }) + .then(() => { + dc.main.ensureConnected() + }) } + private _dcCreationPromise: Record> = {} async _getOtherDc(dcId: number): Promise { if (!this._dcConnections[dcId]) { + if (dcId in this._dcCreationPromise) { + this._log.debug('waiting for DC %d to be created', dcId) + await this._dcCreationPromise[dcId] + + return this._dcConnections[dcId] + } + + const promise = createControllablePromise() + this._dcCreationPromise[dcId] = promise + this._log.debug('creating new DC %d', dcId) - const dcOption = await this.config.findOption({ - dcId, - allowIpv6: this.params.useIpv6, - preferIpv6: this.params.useIpv6, - allowMedia: false, - cdn: false, - }) + try { + const dcOption = await this.config.findOption({ + dcId, + allowIpv6: this.params.useIpv6, + preferIpv6: this.params.useIpv6, + allowMedia: true, + preferMedia: true, + cdn: false, + }) - if (!dcOption) { - throw new Error(`Could not find DC ${dcId}`) + if (!dcOption) { + throw new Error(`Could not find DC ${dcId}`) + } + const dc = new DcConnectionManager(this, dcId, dcOption) + + if (!(await dc.loadKeys())) { + dc.main.requestAuth() + } + + this._dcConnections[dcId] = dc + promise.resolve() + } catch (e) { + promise.reject(e) } - const dc = new DcConnectionManager(this, dcId, dcOption) - - if (!(await dc.loadKeys())) { - dc.main.requestAuth() - } - - this._dcConnections[dcId] = dc } return this._dcConnections[dcId] @@ -532,7 +605,7 @@ export class NetworkManager { const dc = new DcConnectionManager(this, defaultDc.id, defaultDc) this._dcConnections[defaultDc.id] = dc - await this._switchPrimaryDc(dc) + this._switchPrimaryDc(dc) } private async _exportAuthTo(manager: DcConnectionManager): Promise { @@ -575,16 +648,28 @@ export class NetworkManager { } } + setIsPremium(isPremium: boolean): void { + this._log.debug('setting isPremium to %s', isPremium) + this.params.isPremium = isPremium + Object.values(this._dcConnections).forEach((dc) => { + dc.setIsPremium(isPremium) + }) + } + async notifyLoggedIn(auth: tl.auth.TypeAuthorization): Promise { if ( auth._ === 'auth.authorizationSignUpRequired' || auth.user._ === 'userEmpty' - ) { return } + ) { + return + } if (auth.tmpSessions) { this._primaryDc?.main.setCount(auth.tmpSessions) } + this.setIsPremium(auth.user.premium!) + await this.exportAuth() } @@ -689,8 +774,12 @@ export class NetworkManager { } catch (e: any) { lastError = e - if (e instanceof tl.errors.InternalError) { - this._log.warn('Telegram is having internal issues: %s', e) + if (e.code && !(e.code in CLIENT_ERRORS)) { + this._log.warn( + 'Telegram is having internal issues: %d %s, retrying', + e.code, + e.message, + ) if (e.message === 'WORKER_BUSY_TOO_LONG_RETRY') { // according to tdlib, "it is dangerous to resend query without timeout, so use 1" @@ -769,6 +858,32 @@ export class NetworkManager { }) } + getPoolSize(kind: ConnectionKind, dcId?: number) { + const dc = dcId ? this._dcConnections[dcId] : this._primaryDc + + if (!dc) { + if (!this._primaryDc) { + throw new Error('Not connected to any DC') + } + + // guess based on the provided delegate. it is most likely correct, + // but we should give actual values if possible + return this._connectionCount( + kind, + dcId ?? this._primaryDc.dcId, + this.params.isPremium, + ) + } + + return dc[kind].getPoolSize() + } + + getPrimaryDcId() { + if (!this._primaryDc) throw new Error('Not connected to any DC') + + return this._primaryDc.dcId + } + destroy(): void { for (const dc of Object.values(this._dcConnections)) { dc.main.destroy() diff --git a/packages/core/src/network/persistent-connection.ts b/packages/core/src/network/persistent-connection.ts index 61bb5489..12d5a170 100644 --- a/packages/core/src/network/persistent-connection.ts +++ b/packages/core/src/network/persistent-connection.ts @@ -86,11 +86,29 @@ export abstract class PersistentConnection extends EventEmitter { onTransportReady(): void { // transport ready does not mean actual mtproto is ready - if (this._sendOnceConnected.length) { - this._transport.send(Buffer.concat(this._sendOnceConnected)) + const sendNext = () => { + if (!this._sendOnceConnected.length) { + this.onConnected() + + return + } + + const data = this._sendOnceConnected.shift()! + this._transport + .send(data) + .then(sendNext) + .catch((err) => { + this.log.error('error sending queued data: %s', err) + this._sendOnceConnected.unshift(data) + }) + } + + sendNext() + + return } - this._sendOnceConnected = [] + this.onConnected() } @@ -125,7 +143,12 @@ export abstract class PersistentConnection extends EventEmitter { this._consequentFails, this._previousWait, ) - if (wait === false) return this.destroy() + + if (wait === false) { + this.destroy() + + return + } this.emit('wait', wait) diff --git a/packages/core/src/network/session-connection.ts b/packages/core/src/network/session-connection.ts index 546fc64c..4145012d 100644 --- a/packages/core/src/network/session-connection.ts +++ b/packages/core/src/network/session-connection.ts @@ -75,19 +75,13 @@ export class SessionConnection extends PersistentConnection { NodeJS.Timeout ][] = [] - private _next429Timeout = 1000 - private _current429Timeout?: NodeJS.Timeout - - private _lastPingRtt = NaN - private _lastPingTime = 0 - private _lastPingMsgId = Long.ZERO - private _lastSessionCreatedUid = Long.ZERO - private _usePfs = this.params.usePfs ?? false private _isPfsBindingPending = false private _isPfsBindingPendingInBackground = false private _pfsUpdateTimeout?: NodeJS.Timeout + private _inactivityPendingFlush = false + private _readerMap: TlReaderMap private _writerMap: TlWriterMap @@ -149,9 +143,7 @@ export class SessionConnection extends PersistentConnection { reset(forever = false): void { this._session.initConnectionCalled = false - this._resetLastPing(true) this._flushTimer.reset() - clearTimeout(this._current429Timeout!) if (forever) { this.removeAllListeners() @@ -242,26 +234,15 @@ export class SessionConnection extends PersistentConnection { this._onAllFailed(`transport error ${error.code}`) if (error.code === 429) { - // all active queries must be resent - const timeout = this._next429Timeout - - this._next429Timeout = Math.min(this._next429Timeout * 2, 16000) - clearTimeout(this._current429Timeout!) - this._current429Timeout = setTimeout(() => { - this._current429Timeout = undefined - this._flushTimer.emitNow() - }, timeout) - - this.log.debug( - 'transport flood, waiting for %d ms before proceeding', - timeout, + this._session.onTransportFlood( + this.emit.bind(this, 'flood-done'), ) return } } - this.emit('api-error', error) + this.emit('error', error) } protected onConnectionUsable() { @@ -622,9 +603,7 @@ export class SessionConnection extends PersistentConnection { // rpc_result message.uint() - this._sendAck(messageId) - - return this._onRpcResult(message) + return this._onRpcResult(messageId, message) } // we are safe.. i guess @@ -648,7 +627,7 @@ export class SessionConnection extends PersistentConnection { } const message = message_ as mtp.TlObject - this.log.verbose('received %s (msg_id: %l)', message._, messageId) + this.log.debug('received %s (msg_id: %l)', message._, messageId) this._session.recentIncomingMsgIds.add(messageId) switch (message._) { @@ -738,7 +717,7 @@ export class SessionConnection extends PersistentConnection { } } - private _onRpcResult(message: TlBinaryReader): void { + private _onRpcResult(messageId: Long, message: TlBinaryReader): void { if (this._usable && this.params.inactivityTimeout) { this._rescheduleInactivity() } @@ -790,9 +769,12 @@ export class SessionConnection extends PersistentConnection { return } + this._sendAck(messageId) + // special case for auth key binding if (msg._ !== 'rpc') { if (msg._ === 'bind') { + this._sendAck(messageId) msg.promise.resolve(message.object()) return @@ -902,7 +884,9 @@ export class SessionConnection extends PersistentConnection { const msg = this._session.pendingMessages.get(msgId) if (!msg) { - this.log.warn('received ack for unknown message %l', msgId) + if (!this._session.recentOutgoingMsgIds.has(msgId)) { + this.log.warn('received ack for unknown message %l', msgId) + } return } @@ -969,7 +953,6 @@ export class SessionConnection extends PersistentConnection { // e.g. when server returns 429 // most service messages can be omitted as stale - this._resetLastPing(true) for (const msgId of this._session.pendingMessages.keys()) { const info = this._session.pendingMessages.get(msgId)! @@ -1025,7 +1008,7 @@ export class SessionConnection extends PersistentConnection { reason, ) // restart ping - this._resetLastPing(true) + this._session.resetLastPing(true) break case 'rpc': { @@ -1093,16 +1076,6 @@ export class SessionConnection extends PersistentConnection { this._session.pendingMessages.delete(msgId) } - private _resetLastPing(withTime = false): void { - if (withTime) this._lastPingTime = 0 - - if (!this._lastPingMsgId.isZero()) { - this._session.pendingMessages.delete(this._lastPingMsgId) - } - - this._lastPingMsgId = Long.ZERO - } - private _registerOutgoingMsgId(msgId: Long): Long { this._session.recentOutgoingMsgIds.add(msgId) @@ -1142,8 +1115,8 @@ export class SessionConnection extends PersistentConnection { ) } - const rtt = Date.now() - this._lastPingTime - this._lastPingRtt = rtt + const rtt = Date.now() - this._session.lastPingTime + this._session.lastPingRtt = rtt if (info.containerId.neq(msgId)) { this._onMessageAcked(info.containerId) @@ -1155,7 +1128,7 @@ export class SessionConnection extends PersistentConnection { pingId, rtt, ) - this._resetLastPing() + this._session.resetLastPing() } private _onBadServerSalt(msg: mtp.RawMt_bad_server_salt): void { @@ -1211,7 +1184,7 @@ export class SessionConnection extends PersistentConnection { serverSalt, uniqueId, }: mtp.RawMt_new_session_created): void { - if (uniqueId.eq(this._lastSessionCreatedUid)) { + if (uniqueId.eq(this._session.lastSessionCreatedUid)) { this.log.debug( 'received new_session_created with the same uid = %l, ignoring', uniqueId, @@ -1221,7 +1194,7 @@ export class SessionConnection extends PersistentConnection { } if ( - !this._lastSessionCreatedUid.isZero() && + !this._session.lastSessionCreatedUid.isZero() && !this.params.disableUpdates ) { // force the client to fetch missed updates @@ -1277,10 +1250,12 @@ export class SessionConnection extends PersistentConnection { const info = this._session.pendingMessages.get(msgId) if (!info) { - this.log.info( - 'received message info about unknown message %l', - msgId, - ) + if (!this._session.recentOutgoingMsgIds.has(msgId)) { + this.log.warn( + 'received message info about unknown message %l', + msgId, + ) + } return } @@ -1417,7 +1392,7 @@ export class SessionConnection extends PersistentConnection { this._session.queuedAcks.push(msgId) if (this._session.queuedAcks.length >= 100) { - this._flushTimer.emitNow() + this._flushTimer.emitWhenIdle() } } @@ -1586,49 +1561,36 @@ export class SessionConnection extends PersistentConnection { } } - private get _hasPendingServiceMessages(): boolean { - return Boolean( - this._session.queuedRpc.length || - this._session.queuedAcks.length || - this._session.queuedStateReq.length || - this._session.queuedResendReq.length, - ) - } - protected _onInactivityTimeout() { // we should send all pending acks and other service messages // before dropping the connection - if (!this._hasPendingServiceMessages) { + if (!this._session.hasPendingMessages) { this.log.debug('no pending service messages, closing connection') super._onInactivityTimeout() return } - this._flush(() => { - if (this._hasPendingServiceMessages) { - // the callback will be called again once all pending messages are sent - return - } - - this.log.debug('pending service messages sent, closing connection') - this._flushTimer.reset() - super._onInactivityTimeout() - }) + this._inactivityPendingFlush = true + this._flush() } - private _flush(callback?: () => void): void { + flushWhenIdle(): void { + this._flushTimer.emitWhenIdle() + } + + private _flush(): void { if ( !this._session._authKey.ready || this._isPfsBindingPending || - this._current429Timeout + this._session.current429Timeout ) { this.log.debug( 'skipping flush, connection is not usable (auth key ready = %b, pfs binding pending = %b, 429 timeout = %b)', this._session._authKey.ready, this._isPfsBindingPending, - Boolean(this._current429Timeout), + Boolean(this._session.current429Timeout), ) // it will be flushed once connection is usable @@ -1636,7 +1598,7 @@ export class SessionConnection extends PersistentConnection { } try { - this._doFlush(callback) + this._doFlush() } catch (e: any) { this.log.error('flush error: %s', e.stack) // should not happen unless there's a bug in the code @@ -1645,19 +1607,22 @@ export class SessionConnection extends PersistentConnection { // schedule next flush // if there are more queued requests, flush immediately // (they likely didn't fit into one message) - if ( - this._session.queuedRpc.length || - this._session.queuedAcks.length || - this._session.queuedStateReq.length || - this._session.queuedResendReq.length - ) { - this._flush(callback) + if (this._session.hasPendingMessages) { + // we schedule it on the next tick, so we can load-balance + // between multiple connections using the same session + this._flushTimer.emitWhenIdle() + } else if (this._inactivityPendingFlush) { + this.log.debug('pending messages sent, closing connection') + this._flushTimer.reset() + this._inactivityPendingFlush = false + + super._onInactivityTimeout() } else { - this._flushTimer.emitBefore(this._lastPingTime + 60000) + this._flushTimer.emitBefore(this._session.lastPingTime + 60000) } } - private _doFlush(callback?: () => void): void { + private _doFlush(): void { this.log.debug( 'flushing send queue. queued rpc: %d', this._session.queuedRpc.length, @@ -1716,13 +1681,15 @@ export class SessionConnection extends PersistentConnection { const getStateTime = now + 1500 - if (now - this._lastPingTime > 60000) { - if (!this._lastPingMsgId.isZero()) { + if (now - this._session.lastPingTime > 60000) { + if (!this._session.lastPingMsgId.isZero()) { this.log.warn( "didn't receive pong for previous ping (msg_id = %l)", - this._lastPingMsgId, + this._session.lastPingMsgId, + ) + this._session.pendingMessages.delete( + this._session.lastPingMsgId, ) - this._session.pendingMessages.delete(this._lastPingMsgId) } pingId = randomLong() @@ -1731,7 +1698,7 @@ export class SessionConnection extends PersistentConnection { pingId, } - this._lastPingTime = Date.now() + this._session.lastPingTime = Date.now() pingRequest = TlBinaryWriter.serializeObject(this._writerMap, obj) containerSize += pingRequest.length + 16 @@ -1836,13 +1803,17 @@ export class SessionConnection extends PersistentConnection { // if message was already assigned a msg_id, // we must wrap it in a container with a newer msg_id if (msg.msgId) forceContainer = true + + // having >1 upload.getFile within a container seems to cause flood_wait errors + // also a crutch for load-balancing + if (msg.method === 'upload.getFile') break } packetSize += containerSize messageCount += containerMessageCount + rpcToSend.length if (!messageCount) { - this.log.debug('flush failed: nothing to flush') + this.log.debug('flush did not happen: nothing to flush') return } @@ -1875,7 +1846,7 @@ export class SessionConnection extends PersistentConnection { pingMsgId = this._registerOutgoingMsgId( this._session.writeMessage(writer, pingRequest), ) - this._lastPingMsgId = pingMsgId + this._session.lastPingMsgId = pingMsgId const pingPending: PendingMessage = { _: 'ping', pingId: pingId!, @@ -2065,7 +2036,6 @@ export class SessionConnection extends PersistentConnection { this._session .encryptMessage(result) .then((enc) => this.send(enc)) - .then(callback) .catch((err) => { this.log.error( 'error while sending pending messages (root msg_id = %l): %s', diff --git a/packages/core/src/network/transports/tcp.ts b/packages/core/src/network/transports/tcp.ts index 95f4b70e..d0b2b6d2 100644 --- a/packages/core/src/network/transports/tcp.ts +++ b/packages/core/src/network/transports/tcp.ts @@ -48,7 +48,9 @@ export abstract class BaseTcpTransport // eslint-disable-next-line @typescript-eslint/no-unused-vars connect(dc: tl.RawDcOption, testMode: boolean): void { - if (this._state !== TransportState.Idle) { throw new Error('Transport is not IDLE') } + if (this._state !== TransportState.Idle) { + throw new Error('Transport is not IDLE') + } if (!this.packetCodecInitialized) { this._packetCodec.setup?.(this._crypto, this.log) @@ -69,7 +71,9 @@ export abstract class BaseTcpTransport this.handleConnect.bind(this), ) - this._socket.on('data', (data) => this._packetCodec.feed(data)) + this._socket.on('data', (data) => { + this._packetCodec.feed(data) + }) this._socket.on('error', this.handleError.bind(this)) this._socket.on('close', this.close.bind(this)) } @@ -87,7 +91,7 @@ export abstract class BaseTcpTransport this._packetCodec.reset() } - async handleError(error: Error): Promise { + handleError(error: Error): void { this.log.error('error: %s', error.stack) this.emit('error', error) } @@ -99,7 +103,11 @@ export abstract class BaseTcpTransport if (initialMessage.length) { this._socket!.write(initialMessage, (err) => { if (err) { - this.emit('error', err) + this.log.error( + 'failed to write initial message: %s', + err.stack, + ) + this.emit('error') this.close() } else { this._state = TransportState.Ready @@ -113,12 +121,20 @@ export abstract class BaseTcpTransport } async send(bytes: Buffer): Promise { - if (this._state !== TransportState.Ready) { throw new Error('Transport is not READY') } + if (this._state !== TransportState.Ready) { + throw new Error('Transport is not READY') + } const framed = await this._packetCodec.encode(bytes) - return new Promise((res, rej) => { - this._socket!.write(framed, (err) => (err ? rej(err) : res())) + return new Promise((resolve, reject) => { + this._socket!.write(framed, (error) => { + if (error) { + reject(error) + } else { + resolve() + } + }) }) } } diff --git a/packages/core/src/utils/crypto/abstract.ts b/packages/core/src/utils/crypto/abstract.ts index 30291113..78d1e022 100644 --- a/packages/core/src/utils/crypto/abstract.ts +++ b/packages/core/src/utils/crypto/abstract.ts @@ -8,12 +8,6 @@ export interface IEncryptionScheme { decrypt(data: Buffer): MaybeAsync } -export interface IHashMethod { - update(data: Buffer): MaybeAsync - - digest(): MaybeAsync -} - export interface ICryptoProvider { initialize?(): MaybeAsync @@ -38,8 +32,6 @@ export interface ICryptoProvider { createAesEcb(key: Buffer): IEncryptionScheme - createMd5(): IHashMethod - factorizePQ(pq: Buffer): MaybeAsync<[Buffer, Buffer]> } diff --git a/packages/core/src/utils/crypto/forge-crypto.ts b/packages/core/src/utils/crypto/forge-crypto.ts index 16808abd..1407c817 100644 --- a/packages/core/src/utils/crypto/forge-crypto.ts +++ b/packages/core/src/utils/crypto/forge-crypto.ts @@ -3,7 +3,6 @@ import { BaseCryptoProvider, ICryptoProvider, IEncryptionScheme, - IHashMethod, } from './abstract' // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -108,15 +107,6 @@ export class ForgeCryptoProvider ) } - createMd5(): IHashMethod { - const hash = forge.md.md5.create() - - return { - update: (data) => hash.update(data.toString('binary')), - digest: () => Buffer.from(hash.digest().data, 'binary'), - } - } - hmacSha256(data: Buffer, key: Buffer): MaybeAsync { const hmac = forge.hmac.create() hmac.start('sha256', key.toString('binary')) diff --git a/packages/core/src/utils/crypto/node-crypto.ts b/packages/core/src/utils/crypto/node-crypto.ts index 7b09e61b..0d802649 100644 --- a/packages/core/src/utils/crypto/node-crypto.ts +++ b/packages/core/src/utils/crypto/node-crypto.ts @@ -11,7 +11,6 @@ import { BaseCryptoProvider, ICryptoProvider, IEncryptionScheme, - IHashMethod, } from './abstract' export class NodeCryptoProvider @@ -83,10 +82,6 @@ export class NodeCryptoProvider return createHash('sha256').update(data).digest() } - createMd5(): IHashMethod { - return createHash('md5') as unknown as IHashMethod - } - hmacSha256(data: Buffer, key: Buffer): MaybeAsync { return createHmac('sha256', key).update(data).digest() } diff --git a/packages/core/src/utils/logger.ts b/packages/core/src/utils/logger.ts index 3907d5f2..f8a2d836 100644 --- a/packages/core/src/utils/logger.ts +++ b/packages/core/src/utils/logger.ts @@ -84,9 +84,16 @@ export class Logger { if (m === '%j') { return JSON.stringify(val, (k, v) => { - if (typeof v === 'object' && v.type === 'Buffer' && Array.isArray(v.data)) { + if ( + typeof v === 'object' && + v.type === 'Buffer' && + Array.isArray(v.data) + ) { let str = Buffer.from(v.data).toString('base64') - if (str.length > 300) str = str.slice(0, 300) + '...' + + if (str.length > 300) { + str = str.slice(0, 300) + '...' + } return str } @@ -143,10 +150,10 @@ export class LogManager extends Logger { static DEBUG = 4 static VERBOSE = 5 - constructor() { + constructor(tag = 'base') { // workaround because we cant pass this to super // eslint-disable-next-line @typescript-eslint/no-explicit-any - super(null as any, 'base') + super(null as any, tag) // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(this as any).mgr = this } diff --git a/packages/core/tests/crypto-providers.spec.ts b/packages/core/tests/crypto-providers.spec.ts index 1ae337c3..42c6e576 100644 --- a/packages/core/tests/crypto-providers.spec.ts +++ b/packages/core/tests/crypto-providers.spec.ts @@ -164,31 +164,6 @@ export function testCryptoProvider(c: ICryptoProvider): void { '99706487a1cde613bc6de0b6f24b1c7aa448c8b9c3403e3467a8cad89340f53b', ) }) - - it('should calculate md5', async () => { - const test = async (...parts: string[]): Promise => { - const md5 = c.createMd5() - for (const p of parts) await md5.update(Buffer.from(p, 'hex')) - - return md5.digest() - } - - expect((await test()).toString('hex')).eq( - 'd41d8cd98f00b204e9800998ecf8427e', - ) - expect((await test('aaeeff')).toString('hex')).eq( - '9c20ec5e212b4fcfa4666a8b165c6d5d', - ) - expect((await test('aaeeffffeeaa')).toString('hex')).eq( - 'cf216071768a7b610d079e5eb7b68b74', - ) - expect((await test('aaeeff', 'ffeeaa')).toString('hex')).eq( - 'cf216071768a7b610d079e5eb7b68b74', - ) - expect((await test('aa', 'ee', 'ff', 'ffeeaa')).toString('hex')).eq( - 'cf216071768a7b610d079e5eb7b68b74', - ) - }) } describe('NodeCryptoProvider', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ff6af0b..cfa8a90e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,11 +34,11 @@ importers: specifier: 8.5.4 version: 8.5.4 '@typescript-eslint/eslint-plugin': - specifier: 5.59.8 - version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.42.0)(typescript@5.0.4) + specifier: 6.4.0 + version: 6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.0.4) '@typescript-eslint/parser': - specifier: 5.59.8 - version: 5.59.8(eslint@8.42.0)(typescript@5.0.4) + specifier: 6.4.0 + version: 6.4.0(eslint@8.47.0)(typescript@5.0.4) chai: specifier: 4.3.7 version: 4.3.7 @@ -46,23 +46,23 @@ importers: specifier: 3.2.0 version: 3.2.0 eslint: - specifier: 8.42.0 - version: 8.42.0 + specifier: 8.47.0 + version: 8.47.0 eslint-config-prettier: specifier: 8.8.0 - version: 8.8.0(eslint@8.42.0) + version: 8.8.0(eslint@8.47.0) eslint-import-resolver-typescript: - specifier: 3.5.5 - version: 3.5.5(@typescript-eslint/parser@5.59.8)(eslint-plugin-import@2.27.5)(eslint@8.42.0) + specifier: 3.6.0 + version: 3.6.0(@typescript-eslint/parser@6.4.0)(eslint-plugin-import@2.28.0)(eslint@8.47.0) eslint-plugin-ascii: specifier: 1.0.0 version: 1.0.0 eslint-plugin-import: - specifier: 2.27.5 - version: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0) + specifier: 2.28.0 + version: 2.28.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0) eslint-plugin-simple-import-sort: specifier: 10.0.0 - version: 10.0.0(eslint@8.42.0) + version: 10.0.0(eslint@8.47.0) glob: specifier: 10.2.6 version: 10.2.6 @@ -351,6 +351,11 @@ importers: packages: + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + dev: true + /@ampproject/remapping@2.2.0: resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} engines: {node: '>=6.0.0'} @@ -723,14 +728,14 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.42.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.47.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.42.0 - eslint-visitor-keys: 3.4.1 + eslint: 8.47.0 + eslint-visitor-keys: 3.4.3 dev: true /@eslint-community/regexpp@4.5.1: @@ -738,13 +743,18 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.0.3: - resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} + /@eslint-community/regexpp@4.6.2: + resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.1.2: + resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4(supports-color@8.1.1) - espree: 9.5.2 + espree: 9.6.1 globals: 13.20.0 ignore: 5.2.0 import-fresh: 3.3.0 @@ -755,8 +765,8 @@ packages: - supports-color dev: true - /@eslint/js@8.42.0: - resolution: {integrity: sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==} + /@eslint/js@8.47.0: + resolution: {integrity: sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -886,18 +896,6 @@ packages: dev: true optional: true - /@pkgr/utils@2.4.1: - resolution: {integrity: sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - dependencies: - cross-spawn: 7.0.3 - fast-glob: 3.2.12 - is-glob: 4.0.3 - open: 9.1.0 - picocolors: 1.0.0 - tslib: 2.5.3 - dev: true - /@tokenizer/token@0.3.0: resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} dev: false @@ -941,8 +939,8 @@ packages: resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} dev: true - /@types/json-schema@7.0.11: - resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} dev: true /@types/json5@0.0.29: @@ -984,133 +982,134 @@ packages: '@types/node': 18.16.0 dev: true - /@typescript-eslint/eslint-plugin@5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.42.0)(typescript@5.0.4): - resolution: {integrity: sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/eslint-plugin@6.4.0(@typescript-eslint/parser@6.4.0)(eslint@8.47.0)(typescript@5.0.4): + resolution: {integrity: sha512-62o2Hmc7Gs3p8SLfbXcipjWAa6qk2wZGChXG2JbBtYpwSRmti/9KHLqfbLs9uDigOexG+3PaQ9G2g3201FWLKg==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 5.59.8(eslint@8.42.0)(typescript@5.0.4) - '@typescript-eslint/scope-manager': 5.59.8 - '@typescript-eslint/type-utils': 5.59.8(eslint@8.42.0)(typescript@5.0.4) - '@typescript-eslint/utils': 5.59.8(eslint@8.42.0)(typescript@5.0.4) + '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.0.4) + '@typescript-eslint/scope-manager': 6.4.0 + '@typescript-eslint/type-utils': 6.4.0(eslint@8.47.0)(typescript@5.0.4) + '@typescript-eslint/utils': 6.4.0(eslint@8.47.0)(typescript@5.0.4) + '@typescript-eslint/visitor-keys': 6.4.0 debug: 4.3.4(supports-color@8.1.1) - eslint: 8.42.0 - grapheme-splitter: 1.0.4 - ignore: 5.2.0 - natural-compare-lite: 1.4.0 - semver: 7.5.1 - tsutils: 3.21.0(typescript@5.0.4) + eslint: 8.47.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@5.59.8(eslint@8.42.0)(typescript@5.0.4): - resolution: {integrity: sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/parser@6.4.0(eslint@8.47.0)(typescript@5.0.4): + resolution: {integrity: sha512-I1Ah1irl033uxjxO9Xql7+biL3YD7w9IU8zF+xlzD/YxY6a4b7DYA08PXUUCbm2sEljwJF6ERFy2kTGAGcNilg==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.59.8 - '@typescript-eslint/types': 5.59.8 - '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) + '@typescript-eslint/scope-manager': 6.4.0 + '@typescript-eslint/types': 6.4.0 + '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.0.4) + '@typescript-eslint/visitor-keys': 6.4.0 debug: 4.3.4(supports-color@8.1.1) - eslint: 8.42.0 + eslint: 8.47.0 typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@5.59.8: - resolution: {integrity: sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/scope-manager@6.4.0: + resolution: {integrity: sha512-TUS7vaKkPWDVvl7GDNHFQMsMruD+zhkd3SdVW0d7b+7Zo+bd/hXJQ8nsiUZMi1jloWo6c9qt3B7Sqo+flC1nig==} + engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 5.59.8 - '@typescript-eslint/visitor-keys': 5.59.8 + '@typescript-eslint/types': 6.4.0 + '@typescript-eslint/visitor-keys': 6.4.0 dev: true - /@typescript-eslint/type-utils@5.59.8(eslint@8.42.0)(typescript@5.0.4): - resolution: {integrity: sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/type-utils@6.4.0(eslint@8.47.0)(typescript@5.0.4): + resolution: {integrity: sha512-TvqrUFFyGY0cX3WgDHcdl2/mMCWCDv/0thTtx/ODMY1QhEiyFtv/OlLaNIiYLwRpAxAtOLOY9SUf1H3Q3dlwAg==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: '*' + eslint: ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) - '@typescript-eslint/utils': 5.59.8(eslint@8.42.0)(typescript@5.0.4) + '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.0.4) + '@typescript-eslint/utils': 6.4.0(eslint@8.47.0)(typescript@5.0.4) debug: 4.3.4(supports-color@8.1.1) - eslint: 8.42.0 - tsutils: 3.21.0(typescript@5.0.4) + eslint: 8.47.0 + ts-api-utils: 1.0.1(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@5.59.8: - resolution: {integrity: sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/types@6.4.0: + resolution: {integrity: sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg==} + engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@5.59.8(typescript@5.0.4): - resolution: {integrity: sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/typescript-estree@6.4.0(typescript@5.0.4): + resolution: {integrity: sha512-iDPJArf/K2sxvjOR6skeUCNgHR/tCQXBsa+ee1/clRKr3olZjZ/dSkXPZjG6YkPtnW6p5D1egeEPMCW6Gn4yLA==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.59.8 - '@typescript-eslint/visitor-keys': 5.59.8 + '@typescript-eslint/types': 6.4.0 + '@typescript-eslint/visitor-keys': 6.4.0 debug: 4.3.4(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.1 - tsutils: 3.21.0(typescript@5.0.4) + semver: 7.5.4 + ts-api-utils: 1.0.1(typescript@5.0.4) typescript: 5.0.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@5.59.8(eslint@8.42.0)(typescript@5.0.4): - resolution: {integrity: sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/utils@6.4.0(eslint@8.47.0)(typescript@5.0.4): + resolution: {integrity: sha512-BvvwryBQpECPGo8PwF/y/q+yacg8Hn/2XS+DqL/oRsOPK+RPt29h5Ui5dqOKHDlbXrAeHUTnyG3wZA0KTDxRZw==} + engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.42.0) - '@types/json-schema': 7.0.11 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@types/json-schema': 7.0.12 '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 5.59.8 - '@typescript-eslint/types': 5.59.8 - '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.0.4) - eslint: 8.42.0 - eslint-scope: 5.1.1 - semver: 7.5.1 + '@typescript-eslint/scope-manager': 6.4.0 + '@typescript-eslint/types': 6.4.0 + '@typescript-eslint/typescript-estree': 6.4.0(typescript@5.0.4) + eslint: 8.47.0 + semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@5.59.8: - resolution: {integrity: sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@typescript-eslint/visitor-keys@6.4.0: + resolution: {integrity: sha512-yJSfyT+uJm+JRDWYRYdCm2i+pmvXJSMtPR9Cq5/XQs4QIgNoLcoRtDdzsLbLsFM/c6um6ohQkg/MLxWvoIndJA==} + engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 5.59.8 + '@typescript-eslint/types': 6.4.0 eslint-visitor-keys: 3.4.1 dev: true @@ -1126,12 +1125,12 @@ packages: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: false - /acorn-jsx@5.3.2(acorn@8.8.2): + /acorn-jsx@5.3.2(acorn@8.10.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.8.2 + acorn: 8.10.0 dev: true /acorn-walk@8.2.0: @@ -1139,6 +1138,12 @@ packages: engines: {node: '>=0.4.0'} dev: true + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} @@ -1322,6 +1327,17 @@ packages: engines: {node: '>=8'} dev: true + /array.prototype.findlastindex@1.2.2: + resolution: {integrity: sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + es-shim-unscopables: 1.0.0 + get-intrinsic: 1.2.1 + dev: true + /array.prototype.flat@1.3.1: resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} engines: {node: '>= 0.4'} @@ -1379,6 +1395,7 @@ packages: /big-integer@1.6.51: resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} engines: {node: '>=0.6'} + dev: false /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} @@ -1403,13 +1420,6 @@ packages: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: true - /bplist-parser@0.2.0: - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} - engines: {node: '>= 5.10.0'} - dependencies: - big-integer: 1.6.51 - dev: true - /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -1451,13 +1461,6 @@ packages: ieee754: 1.2.1 dev: false - /bundle-name@3.0.0: - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} - engines: {node: '>=12'} - dependencies: - run-applescript: 5.0.0 - dev: true - /cacache@16.0.7: resolution: {integrity: sha512-a4zfQpp5vm4Ipdvbj+ZrPonikRhm6WBEd4zT1Yc1DXsmAxrPgDwWBLF/u/wTVXSFPIgOJ1U3ghSa2Xm4s3h28w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -1905,24 +1908,6 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /default-browser-id@3.0.0: - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} - engines: {node: '>=12'} - dependencies: - bplist-parser: 0.2.0 - untildify: 4.0.0 - dev: true - - /default-browser@4.0.0: - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} - engines: {node: '>=14.16'} - dependencies: - bundle-name: 3.0.0 - default-browser-id: 3.0.0 - execa: 7.1.1 - titleize: 3.0.0 - dev: true - /default-require-extensions@3.0.0: resolution: {integrity: sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==} engines: {node: '>=8'} @@ -1930,11 +1915,6 @@ packages: strip-bom: 4.0.0 dev: true - /define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - dev: true - /define-properties@1.2.0: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} @@ -2204,13 +2184,13 @@ packages: engines: {node: '>=10'} dev: true - /eslint-config-prettier@8.8.0(eslint@8.42.0): + /eslint-config-prettier@8.8.0(eslint@8.47.0): resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.42.0 + eslint: 8.47.0 dev: true /eslint-import-resolver-node@0.3.7: @@ -2218,13 +2198,13 @@ packages: dependencies: debug: 3.2.7 is-core-module: 2.12.1 - resolve: 1.22.2 + resolve: 1.22.4 transitivePeerDependencies: - supports-color dev: true - /eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.59.8)(eslint-plugin-import@2.27.5)(eslint@8.42.0): - resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} + /eslint-import-resolver-typescript@3.6.0(@typescript-eslint/parser@6.4.0)(eslint-plugin-import@2.28.0)(eslint@8.47.0): + resolution: {integrity: sha512-QTHR9ddNnn35RTxlaEnx2gCxqFlF2SEN0SE2d17SqwyM7YOSI2GHWRYp5BiRkObTUNYPupC/3Fq2a0PpT+EKpg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: eslint: '*' @@ -2232,14 +2212,13 @@ packages: dependencies: debug: 4.3.4(supports-color@8.1.1) enhanced-resolve: 5.14.1 - eslint: 8.42.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0) + eslint: 8.47.0 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0) + eslint-plugin-import: 2.28.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0) + fast-glob: 3.3.1 get-tsconfig: 4.6.0 - globby: 13.1.4 is-core-module: 2.12.1 is-glob: 4.0.3 - synckit: 0.8.5 transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -2247,7 +2226,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -2268,11 +2247,11 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.59.8(eslint@8.42.0)(typescript@5.0.4) + '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.0.4) debug: 3.2.7 - eslint: 8.42.0 + eslint: 8.47.0 eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.8)(eslint-plugin-import@2.27.5)(eslint@8.42.0) + eslint-import-resolver-typescript: 3.6.0(@typescript-eslint/parser@6.4.0)(eslint-plugin-import@2.28.0)(eslint@8.47.0) transitivePeerDependencies: - supports-color dev: true @@ -2281,8 +2260,8 @@ packages: resolution: {integrity: sha512-NT2asS7tLkXMKBk0GuX6eDUZvb5DWTFDCt7R6a8tvWs5P0my2ybxmCFy3Afxgdcam+wQRAn8JrldLmcK0H5Axg==} dev: true - /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0): - resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + /eslint-plugin-import@2.28.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0): + resolution: {integrity: sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -2291,22 +2270,25 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.59.8(eslint@8.42.0)(typescript@5.0.4) + '@typescript-eslint/parser': 6.4.0(eslint@8.47.0)(typescript@5.0.4) array-includes: 3.1.6 + array.prototype.findlastindex: 1.2.2 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.42.0 + eslint: 8.47.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.42.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0) has: 1.0.3 is-core-module: 2.12.1 is-glob: 4.0.3 minimatch: 3.1.2 + object.fromentries: 2.0.6 + object.groupby: 1.0.0 object.values: 1.1.6 - resolve: 1.22.2 - semver: 6.3.0 + resolve: 1.22.4 + semver: 6.3.1 tsconfig-paths: 3.14.2 transitivePeerDependencies: - eslint-import-resolver-typescript @@ -2314,24 +2296,16 @@ packages: - supports-color dev: true - /eslint-plugin-simple-import-sort@10.0.0(eslint@8.42.0): + /eslint-plugin-simple-import-sort@10.0.0(eslint@8.47.0): resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==} peerDependencies: eslint: '>=5.0.0' dependencies: - eslint: 8.42.0 + eslint: 8.47.0 dev: true - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - dev: true - - /eslint-scope@7.2.0: - resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 @@ -2343,15 +2317,20 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.42.0: - resolution: {integrity: sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==} + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.47.0: + resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.42.0) - '@eslint-community/regexpp': 4.5.1 - '@eslint/eslintrc': 2.0.3 - '@eslint/js': 8.42.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@eslint-community/regexpp': 4.6.2 + '@eslint/eslintrc': 2.1.2 + '@eslint/js': 8.47.0 '@humanwhocodes/config-array': 0.11.10 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -2361,9 +2340,9 @@ packages: debug: 4.3.4(supports-color@8.1.1) doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.0 - eslint-visitor-keys: 3.4.1 - espree: 9.5.2 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 esquery: 1.5.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -2373,7 +2352,6 @@ packages: globals: 13.20.0 graphemer: 1.4.0 ignore: 5.2.0 - import-fresh: 3.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 @@ -2383,21 +2361,20 @@ packages: lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 - optionator: 0.9.1 + optionator: 0.9.3 strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 text-table: 0.2.0 transitivePeerDependencies: - supports-color dev: true - /espree@9.5.2: - resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.8.2 - acorn-jsx: 5.3.2(acorn@8.8.2) - eslint-visitor-keys: 3.4.1 + acorn: 8.10.0 + acorn-jsx: 5.3.2(acorn@8.10.0) + eslint-visitor-keys: 3.4.3 dev: true /esprima@4.0.1: @@ -2420,11 +2397,6 @@ packages: estraverse: 5.3.0 dev: true - /estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - dev: true - /estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -2484,8 +2456,8 @@ packages: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true - /fast-glob@3.2.11: - resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} + /fast-glob@3.2.12: + resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2495,8 +2467,8 @@ packages: micromatch: 4.0.5 dev: true - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2834,23 +2806,12 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.2.11 + fast-glob: 3.2.12 ignore: 5.2.0 merge2: 1.4.1 slash: 3.0.0 dev: true - /globby@13.1.4: - resolution: {integrity: sha512-iui/IiiW+QrJ1X1hKH5qwlMQyv34wJAYwH1vrf8b9kBA4sNiif3gKsMHa+BrdnOpEudWjpotfa7LrTzB1ERS/g==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - dir-glob: 3.0.1 - fast-glob: 3.2.11 - ignore: 5.2.0 - merge2: 1.4.1 - slash: 4.0.0 - dev: true - /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: @@ -2860,10 +2821,6 @@ packages: /graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - /grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - dev: true - /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true @@ -3031,6 +2988,11 @@ packages: engines: {node: '>= 4'} dev: true + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -3125,6 +3087,12 @@ packages: has: 1.0.3 dev: true + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + dependencies: + has: 1.0.3 + dev: true + /is-date-object@1.0.5: resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} engines: {node: '>= 0.4'} @@ -3132,18 +3100,6 @@ packages: has-tostringtag: 1.0.0 dev: true - /is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - dev: true - - /is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - dev: true - /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3172,14 +3128,6 @@ packages: is-extglob: 2.1.1 dev: true - /is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - dependencies: - is-docker: 3.0.0 - dev: true - /is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} dev: false @@ -3297,13 +3245,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - dependencies: - is-docker: 2.2.1 - dev: true - /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: false @@ -3887,10 +3828,6 @@ packages: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} dev: false - /natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - dev: true - /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -4079,6 +4016,24 @@ packages: object-keys: 1.1.1 dev: true + /object.fromentries@2.0.6: + resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + dev: true + + /object.groupby@1.0.0: + resolution: {integrity: sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.21.2 + get-intrinsic: 1.2.1 + dev: true + /object.values@1.1.6: resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} engines: {node: '>= 0.4'} @@ -4107,26 +4062,16 @@ packages: mimic-fn: 4.0.0 dev: true - /open@9.1.0: - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} - engines: {node: '>=14.16'} - dependencies: - default-browser: 4.0.0 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - is-wsl: 2.2.0 - dev: true - - /optionator@0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - word-wrap: 1.2.3 dev: true /p-limit@2.3.0: @@ -4514,6 +4459,15 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /resolve@1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} + hasBin: true + dependencies: + is-core-module: 2.13.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + /restore-cursor@3.1.0: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} @@ -4550,13 +4504,6 @@ packages: glob: 10.2.6 dev: true - /run-applescript@5.0.0: - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} - engines: {node: '>=12'} - dependencies: - execa: 5.1.1 - dev: true - /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -4599,6 +4546,11 @@ packages: hasBin: true dev: true + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + dev: true + /semver@7.5.0: resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} engines: {node: '>=10'} @@ -4614,6 +4566,14 @@ packages: dependencies: lru-cache: 6.0.0 + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + /serialize-javascript@6.0.0: resolution: {integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==} dependencies: @@ -4677,11 +4637,6 @@ packages: engines: {node: '>=8'} dev: true - /slash@4.0.0: - resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} - engines: {node: '>=12'} - dev: true - /slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} @@ -4950,14 +4905,6 @@ packages: engines: {node: '>= 0.4'} dev: true - /synckit@0.8.5: - resolution: {integrity: sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==} - engines: {node: ^14.18.0 || >=16.0.0} - dependencies: - '@pkgr/utils': 2.4.1 - tslib: 2.5.3 - dev: true - /tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -5023,11 +4970,6 @@ packages: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true - /titleize@3.0.0: - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} - engines: {node: '>=12'} - dev: true - /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -5053,6 +4995,15 @@ packages: engines: {node: '>=8'} dev: true + /ts-api-utils@1.0.1(typescript@5.0.4): + resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' + dependencies: + typescript: 5.0.4 + dev: true + /ts-node@10.9.1(@types/node@18.16.0)(typescript@5.0.4): resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true @@ -5102,24 +5053,10 @@ packages: strip-bom: 3.0.0 dev: true - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true - /tslib@2.5.3: resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} dev: true - /tsutils@3.21.0(typescript@5.0.4): - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - typescript: 5.0.4 - dev: true - /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -5223,11 +5160,6 @@ packages: engines: {node: '>= 10.0.0'} dev: true - /untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - dev: true - /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -5301,11 +5233,6 @@ packages: string-width: 4.2.3 dev: false - /word-wrap@1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} - dev: true - /workerpool@6.2.1: resolution: {integrity: sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==} dev: true