From 9001296bd545637245d00c0ac94dfb904b09e563 Mon Sep 17 00:00:00 2001 From: oluwaseyi1996-netizen Date: Tue, 29 Sep 2026 20:26:36 +0000 Subject: [PATCH 1/4] fix(listener): repair merge-corrupted sources that broke transpile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The listener as merged in #799 contains duplicated statements and imports that make several source files emit invalid JavaScript. As a result the API module graph could not be loaded at all, and `tsc` aborted before semantic analysis (32 syntax errors, zero real type checking). Collapse each duplicated declaration back to a single implementation: - utils/request-id.ts, middleware/security-headers.ts and services/discord-notification.ts: incomplete statements that terminated a function early and left a dangling block - index.ts: `subscriber` declared twice in the same scope - services/event-subscriber.ts: duplicated `processableEvents` and `request` declarations, plus a `backfillStartLedger` field that was used but never declared - api/events-server.ts and config.ts: duplicated imports / type imports Source files now emit valid JavaScript, so typecheck and the test suite can actually run against them again. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- listener/src/api/events-server.ts | 648 ++++++++++++------ listener/src/config.ts | 102 +-- listener/src/index.ts | 21 +- listener/src/middleware/security-headers.ts | 15 +- listener/src/services/discord-notification.ts | 47 +- listener/src/services/event-subscriber.ts | 77 +-- listener/src/utils/request-id.ts | 3 + 7 files changed, 554 insertions(+), 359 deletions(-) diff --git a/listener/src/api/events-server.ts b/listener/src/api/events-server.ts index d517ad21..d5ddadbc 100644 --- a/listener/src/api/events-server.ts +++ b/listener/src/api/events-server.ts @@ -15,13 +15,14 @@ import { sendOk, sendErr, sendJson, ErrorCode } from '../utils/response'; import { handleApiError, ApiError } from './error-handler'; import { applyRequestContext } from '../utils/request-id'; import { applyRequestIdMiddleware } from '../middleware/request-id'; -import { TemplateService } from '../services/template-service'; -import { handleTemplateRoutes } from './template-routes'; import { NotificationHistoryService } from '../services/notification-history'; import { SearchSuggestionService } from '../services/search-suggestion'; import { NotificationSearchService } from '../services/notification-search-service'; import { collectRawBody, verifyWebhookRequest } from '../services/webhook-verifier'; -import { IdempotencyKeyService, IdempotencyKeyReuseError } from '../services/idempotency-key-service'; +import { + IdempotencyKeyService, + IdempotencyKeyReuseError, +} from '../services/idempotency-key-service'; import { WebhookSecret, RateLimitConfig, ContractConfig } from '../types'; import { RateLimiter } from './rate-limiter'; import { getDatabase } from '../database/database'; @@ -34,9 +35,7 @@ import { TemplateNotFoundError, TemplateValidationError, } from '../services/notification-template-repository'; -import { - TemplateRenderError, -} from '../services/notification-template-service'; +import { TemplateRenderError } from '../services/notification-template-service'; import { parseTemplateUpdateBody, resolveRequestActor, @@ -148,9 +147,8 @@ interface IndexingHealthResponse { detail?: string; } -let cachedNetworkTip: - | { fetchedAt: number; ledger: number | null; errorDetail?: string } - | null = null; +let cachedNetworkTip: { fetchedAt: number; ledger: number | null; errorDetail?: string } | null = + null; function withTimeout(promise: Promise, ms: number): Promise { return Promise.race([ @@ -180,10 +178,7 @@ export async function checkStellarRpc(rpcUrl: string): Promise { export async function checkDiscord(webhookUrl: string): Promise { const start = Date.now(); try { - const response = await withTimeout( - fetch(webhookUrl, { method: 'GET' }), - HEALTH_TIMEOUT_MS - ); + const response = await withTimeout(fetch(webhookUrl, { method: 'GET' }), HEALTH_TIMEOUT_MS); if (response.ok) { return { status: 'ok', latencyMs: Date.now() - start }; } @@ -225,7 +220,7 @@ export async function checkDatabase(): Promise { async function getContractPauseStatus( contractAddress: string, - stellarRpcUrl: string + stellarRpcUrl: string, ): Promise<{ paused: boolean; error?: string }> { try { const server = new StellarSDK.rpc.Server(stellarRpcUrl); @@ -250,12 +245,13 @@ async function getContractPauseStatus( // Check if simulation was successful by looking for error property if ('error' in simulation && simulation.error) { - const errorMsg = typeof simulation.error === 'object' && 'message' in simulation.error - ? (simulation.error as any).message - : 'Failed to simulate contract call'; + const errorMsg = + typeof simulation.error === 'object' && 'message' in simulation.error + ? (simulation.error as any).message + : 'Failed to simulate contract call'; return { paused: false, - error: errorMsg + error: errorMsg, }; } @@ -266,7 +262,7 @@ async function getContractPauseStatus( } catch (err) { return { paused: false, - error: err instanceof Error ? err.message : String(err) + error: err instanceof Error ? err.message : String(err), }; } } @@ -284,14 +280,14 @@ async function buildStatusResponse(options: EventsServerOptions): Promise<{ const status = await getContractPauseStatus(contractConfig.address, options.stellarRpcUrl); return { address: contractConfig.address, - ...status + ...status, }; - }) + }), ); return { timestamp: new Date().toISOString(), - contracts: contractStatuses + contracts: contractStatuses, }; } @@ -299,10 +295,7 @@ async function fetchNetworkTipLedger(rpcUrl: string): Promise<{ ledger: number | null; errorDetail?: string; }> { - if ( - cachedNetworkTip && - Date.now() - cachedNetworkTip.fetchedAt < NETWORK_TIP_CACHE_TTL_MS - ) { + if (cachedNetworkTip && Date.now() - cachedNetworkTip.fetchedAt < NETWORK_TIP_CACHE_TTL_MS) { return { ledger: cachedNetworkTip.ledger, errorDetail: cachedNetworkTip.errorDetail }; } @@ -314,7 +307,7 @@ async function fetchNetworkTipLedger(rpcUrl: string): Promise<{ // We keep extraction defensive to avoid hard-coupling to the SDK response shape. const latest: any = await withTimeout( (server as any).getLatestLedger(), - HEALTH_TIMEOUT_MS + HEALTH_TIMEOUT_MS, ); const ledger = typeof latest?.sequence === 'number' @@ -368,7 +361,7 @@ function deriveIndexingStatus(args: { return { status: 'degraded', detail: `Behind by ${ledgerLag} ledger(s) and last ingestion was ${Math.round( - delay / 1000 + delay / 1000, )}s ago.`, }; } @@ -466,7 +459,10 @@ export function createEventsServer(options: EventsServerOptions): http.Server { 'Access-Control-Allow-Headers', 'Content-Type, X-API-Key, Authorization, X-Correlation-Id, X-Request-Id', ); - res.setHeader('Access-Control-Expose-Headers', 'X-Request-Id, X-Correlation-Id, X-Response-Time'); + res.setHeader( + 'Access-Control-Expose-Headers', + 'X-Request-Id, X-Correlation-Id, X-Response-Time', + ); const url = new URL(req.url ?? '/', 'http://localhost'); const pathname = url.pathname; @@ -520,24 +516,36 @@ export function createEventsServer(options: EventsServerOptions): http.Server { // GET /health if (req.method === 'GET' && url.pathname === '/health') { - buildHealthResponse(options).then((health) => { - const httpStatus = health.status === 'error' ? 503 : 200; - sendJson(res, httpStatus, health); - }).catch((err) => { - logger.error('Health check failed unexpectedly', { error: err, requestId, correlationId }); - sendErr(res, 500, 'Internal health check failure', ErrorCode.INTERNAL_ERROR); - }); + buildHealthResponse(options) + .then((health) => { + const httpStatus = health.status === 'error' ? 503 : 200; + sendJson(res, httpStatus, health); + }) + .catch((err) => { + logger.error('Health check failed unexpectedly', { + error: err, + requestId, + correlationId, + }); + sendErr(res, 500, 'Internal health check failure', ErrorCode.INTERNAL_ERROR); + }); return; } // GET /api/status if (req.method === 'GET' && url.pathname === '/api/status') { - buildStatusResponse(options).then((status) => { - sendOk(res, 200, status); - }).catch((err) => { - logger.error('Status check failed unexpectedly', { error: err, requestId, correlationId }); - sendErr(res, 500, 'Internal status check failure', ErrorCode.INTERNAL_ERROR); - }); + buildStatusResponse(options) + .then((status) => { + sendOk(res, 200, status); + }) + .catch((err) => { + logger.error('Status check failed unexpectedly', { + error: err, + requestId, + correlationId, + }); + sendErr(res, 500, 'Internal status check failure', ErrorCode.INTERNAL_ERROR); + }); return; } @@ -588,7 +596,9 @@ export function createEventsServer(options: EventsServerOptions): http.Server { networkTipLedger, ledgerLag, processingDelayMs, - lastIngestedAt: ingestion.lastIngestedAt ? new Date(ingestion.lastIngestedAt).toISOString() : null, + lastIngestedAt: ingestion.lastIngestedAt + ? new Date(ingestion.lastIngestedAt).toISOString() + : null, detail: derived.detail ?? networkTip.errorDetail, }; @@ -638,8 +648,11 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const sinceParam = url.searchParams.get('since'); const limit = limitParam ? parseInt(limitParam, 10) : undefined; const since = sinceParam ? new Date(sinceParam) : undefined; - options.metricsStore.getHistory(limit, since) - .then((snapshots) => { sendOk(res, 200, { snapshots }); }) + options.metricsStore + .getHistory(limit, since) + .then((snapshots) => { + sendOk(res, 200, { snapshots }); + }) .catch((error) => { logger.error('Failed to fetch metrics history', { error, requestId, correlationId }); sendErr(res, 500, (error as Error).message, ErrorCode.INTERNAL_ERROR); @@ -673,92 +686,106 @@ export function createEventsServer(options: EventsServerOptions): http.Server { res.end(JSON.stringify({ success: false, error: { code, message }, code })); }; - collectRawBody(req).then(async (rawBody) => { - const sourceIp = - (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() || - (req.socket?.remoteAddress as string | undefined); - - const secrets = options.webhookSecrets ?? []; - const maxAgeSeconds = options.signatureExpirationSeconds ?? 300; + collectRawBody(req) + .then(async (rawBody) => { + const sourceIp = + (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() || + (req.socket?.remoteAddress as string | undefined); - const auth = verifyWebhookRequest({ - headers: req.headers as Record, - rawBody, - secrets, - sourceIp, - requestId, - correlationId, - maxAgeSeconds, - }); + const secrets = options.webhookSecrets ?? []; + const maxAgeSeconds = options.signatureExpirationSeconds ?? 300; - if (!auth.authenticated) { - logger.warn('Webhook authentication failed', { + const auth = verifyWebhookRequest({ + headers: req.headers as Record, + rawBody, + secrets, + sourceIp, requestId, correlationId, - code: auth.errorCode, + maxAgeSeconds, }); - writeAuthFailure(auth.statusCode, auth.message, auth.errorCode); - return; - } - try { - const acceptWebhook = async (): Promise<{ status: string; verified: boolean }> => { - logger.info('Webhook received and signature verified', { + if (!auth.authenticated) { + logger.warn('Webhook authentication failed', { requestId, correlationId, - keyId: auth.keyId, - timestampVerified: auth.timestampVerified, - sourceIp, - contentLength: rawBody.length, - idempotencyKey, - }); - return { status: 'accepted', verified: true }; - }; - - if (options.idempotencyService && idempotencyKey) { - const outcome = await options.idempotencyService.processWithIdempotency( - idempotencyKey, - rawBody, - acceptWebhook, - { requestId, correlationId } - ); - const statusCode = outcome.isDuplicate ? 200 : 202; - res.writeHead(statusCode, { - 'Content-Type': 'application/json', - 'X-Idempotent-Replay': outcome.isDuplicate ? 'true' : 'false', - }); - res.end(JSON.stringify({ - ...(outcome.result as object), - replay: outcome.isDuplicate, - })); - } else { - const result = await acceptWebhook(); - res.writeHead(202, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(result)); - } - } catch (err) { - if (err instanceof IdempotencyKeyReuseError) { - logger.warn('Webhook rejected: idempotency key reused with different body', { - requestId, correlationId, idempotencyKey, + code: auth.errorCode, }); - writeAuthFailure(err.statusCode, err.message, err.code); + writeAuthFailure(auth.statusCode, auth.message, auth.errorCode); return; } - logger.error('Failed to process webhook', { requestId, correlationId, error: err }); - sendErr(res, 500, 'Internal server error', ErrorCode.INTERNAL_ERROR); - } - }).catch((err) => { - logger.error('Failed to read webhook body', { requestId, correlationId, error: err instanceof Error ? err.message : String(err) }); - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Failed to read request body', code: 'BODY_READ_FAILED' })); - }); + + try { + const acceptWebhook = async (): Promise<{ status: string; verified: boolean }> => { + logger.info('Webhook received and signature verified', { + requestId, + correlationId, + keyId: auth.keyId, + timestampVerified: auth.timestampVerified, + sourceIp, + contentLength: rawBody.length, + idempotencyKey, + }); + return { status: 'accepted', verified: true }; + }; + + if (options.idempotencyService && idempotencyKey) { + const outcome = await options.idempotencyService.processWithIdempotency( + idempotencyKey, + rawBody, + acceptWebhook, + { requestId, correlationId }, + ); + const statusCode = outcome.isDuplicate ? 200 : 202; + res.writeHead(statusCode, { + 'Content-Type': 'application/json', + 'X-Idempotent-Replay': outcome.isDuplicate ? 'true' : 'false', + }); + res.end( + JSON.stringify({ + ...(outcome.result as object), + replay: outcome.isDuplicate, + }), + ); + } else { + const result = await acceptWebhook(); + res.writeHead(202, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(result)); + } + } catch (err) { + if (err instanceof IdempotencyKeyReuseError) { + logger.warn('Webhook rejected: idempotency key reused with different body', { + requestId, + correlationId, + idempotencyKey, + }); + writeAuthFailure(err.statusCode, err.message, err.code); + return; + } + logger.error('Failed to process webhook', { requestId, correlationId, error: err }); + sendErr(res, 500, 'Internal server error', ErrorCode.INTERNAL_ERROR); + } + }) + .catch((err) => { + logger.error('Failed to read webhook body', { + requestId, + correlationId, + error: err instanceof Error ? err.message : String(err), + }); + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end( + JSON.stringify({ error: 'Failed to read request body', code: 'BODY_READ_FAILED' }), + ); + }); return; } // POST /api/notifications/validate-batch if (req.method === 'POST' && url.pathname === '/api/notifications/validate-batch') { let body = ''; - req.on('data', (chunk) => { body += chunk.toString(); }); + req.on('data', (chunk) => { + body += chunk.toString(); + }); req.on('end', () => { try { const data = JSON.parse(body || 'null'); @@ -768,18 +795,32 @@ export function createEventsServer(options: EventsServerOptions): http.Server { if (!result.valid) { sendOk(res, 400, result); - logger.warn('Batch validation rejected', { requestId, correlationId, errorCount: result.errors.length }); + logger.warn('Batch validation rejected', { + requestId, + correlationId, + errorCount: result.errors.length, + }); return; } sendOk(res, 200, result); - logger.info('Batch validation passed', { requestId, correlationId, processedCount: result.processedCount }); + logger.info('Batch validation passed', { + requestId, + correlationId, + processedCount: result.processedCount, + }); } catch (error) { - logger.error('Failed to validate notification batch', { error, requestId, correlationId }); + logger.error('Failed to validate notification batch', { + error, + requestId, + correlationId, + }); sendOk(res, 400, { valid: false, processedCount: 0, - errors: [{ index: -1, code: 'PARSE_ERROR', message: 'Request body must be valid JSON.' }], + errors: [ + { index: -1, code: 'PARSE_ERROR', message: 'Request body must be valid JSON.' }, + ], }); } }); @@ -804,7 +845,9 @@ export function createEventsServer(options: EventsServerOptions): http.Server { } let body = ''; - req.on('data', (chunk) => { body += chunk.toString(); }); + req.on('data', (chunk) => { + body += chunk.toString(); + }); req.on('end', async () => { try { const contentType = req.headers['content-type'] || ''; @@ -834,21 +877,30 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const idempotencyKey = IdempotencyKeyService.extractKey(req.headers) ?? undefined; let body = ''; - req.on('data', (chunk) => { body += chunk.toString(); }); + req.on('data', (chunk) => { + body += chunk.toString(); + }); req.on('end', async () => { try { const data = JSON.parse(body); if (!data.executeAt || !data.payload || !data.targetRecipient) { res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'Missing required fields: executeAt, payload, targetRecipient', code: 'MISSING_FIELDS' })); + res.end( + JSON.stringify({ + error: 'Missing required fields: executeAt, payload, targetRecipient', + code: 'MISSING_FIELDS', + }), + ); return; } const executeAt = new Date(data.executeAt); if (isNaN(executeAt.getTime())) { res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: 'executeAt is not a valid date', code: 'INVALID_DATE' })); + res.end( + JSON.stringify({ error: 'executeAt is not a valid date', code: 'INVALID_DATE' }), + ); return; } @@ -865,7 +917,10 @@ export function createEventsServer(options: EventsServerOptions): http.Server { metadata: data.metadata, }); logger.info('Notification scheduled via API', { - requestId, correlationId, notificationId, executeAt: data.executeAt, + requestId, + correlationId, + notificationId, + executeAt: data.executeAt, }); return { id: notificationId }; }; @@ -875,17 +930,19 @@ export function createEventsServer(options: EventsServerOptions): http.Server { idempotencyKey, data, schedule, - { requestId, correlationId } + { requestId, correlationId }, ); const statusCode = outcome.isDuplicate ? 200 : 201; res.writeHead(statusCode, { 'Content-Type': 'application/json', 'X-Idempotent-Replay': outcome.isDuplicate ? 'true' : 'false', }); - res.end(JSON.stringify({ - id: (outcome.result as { id: number }).id, - replay: outcome.isDuplicate, - })); + res.end( + JSON.stringify({ + id: (outcome.result as { id: number }).id, + replay: outcome.isDuplicate, + }), + ); return; } @@ -895,7 +952,9 @@ export function createEventsServer(options: EventsServerOptions): http.Server { } catch (error) { if (error instanceof IdempotencyKeyReuseError) { logger.warn('Schedule API rejected request: idempotency key body mismatch', { - requestId, correlationId, idempotencyKey, + requestId, + correlationId, + idempotencyKey, }); res.writeHead(error.statusCode, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: error.message, code: error.code })); @@ -918,7 +977,8 @@ export function createEventsServer(options: EventsServerOptions): http.Server { logger.error('Failed to schedule notification', { error: error instanceof Error ? error.message : String(error), - requestId, correlationId, + requestId, + correlationId, }); res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: (error as Error).message, code: 'SCHEDULE_FAILED' })); @@ -934,8 +994,11 @@ export function createEventsServer(options: EventsServerOptions): http.Server { return; } - options.notificationAPI.getStatistics() - .then((stats) => { sendOk(res, 200, stats); }) + options.notificationAPI + .getStatistics() + .then((stats) => { + sendOk(res, 200, stats); + }) .catch((error) => { logger.error('Failed to get scheduler stats', { error, requestId, correlationId }); sendErr(res, 500, (error as Error).message, ErrorCode.INTERNAL_ERROR); @@ -949,8 +1012,11 @@ export function createEventsServer(options: EventsServerOptions): http.Server { sendErr(res, 503, 'Scheduler not enabled', ErrorCode.SERVICE_UNAVAILABLE); return; } - (options.notificationAPI as any).getExecutionMetrics() - .then((metrics: unknown) => { sendOk(res, 200, metrics); }) + (options.notificationAPI as any) + .getExecutionMetrics() + .then((metrics: unknown) => { + sendOk(res, 200, metrics); + }) .catch((error: Error) => { logger.error('Failed to get execution metrics', { error, requestId, correlationId }); sendErr(res, 500, error.message, ErrorCode.INTERNAL_ERROR); @@ -964,8 +1030,11 @@ export function createEventsServer(options: EventsServerOptions): http.Server { sendErr(res, 503, 'Scheduler not enabled', ErrorCode.SERVICE_UNAVAILABLE); return; } - (options.notificationAPI as any).getRetryDistribution() - .then((distribution: unknown) => { sendOk(res, 200, distribution); }) + (options.notificationAPI as any) + .getRetryDistribution() + .then((distribution: unknown) => { + sendOk(res, 200, distribution); + }) .catch((error: Error) => { logger.error('Failed to get retry distribution', { error, requestId, correlationId }); sendErr(res, 500, error.message, ErrorCode.INTERNAL_ERROR); @@ -991,7 +1060,10 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const monitor = getJobMonitor(); const limitParam = url.searchParams.get('limit'); const limit = limitParam ? Math.min(Math.max(parseInt(limitParam, 10) || 50, 1), 200) : 50; - sendOk(res, 200, { failures: monitor.listFailures(limit), count: monitor.listFailures(limit).length }); + sendOk(res, 200, { + failures: monitor.listFailures(limit), + count: monitor.listFailures(limit).length, + }); return; } @@ -1002,7 +1074,8 @@ export function createEventsServer(options: EventsServerOptions): http.Server { return; } - options.notificationAPI.getRetryStatistics() + options.notificationAPI + .getRetryStatistics() .then((stats) => { sendOk(res, 200, stats); }) @@ -1024,9 +1097,12 @@ export function createEventsServer(options: EventsServerOptions): http.Server { } const limitParam = url.searchParams.get('limit'); - const limit = limitParam ? Math.max(1, Math.min(500, parseInt(limitParam, 10) || 100)) : undefined; + const limit = limitParam + ? Math.max(1, Math.min(500, parseInt(limitParam, 10) || 100)) + : undefined; - options.notificationAPI.getPendingJobs(limit) + options.notificationAPI + .getPendingJobs(limit) .then((jobs) => { logger.info('Handling GET /api/schedule/queue', { requestId, @@ -1056,7 +1132,8 @@ export function createEventsServer(options: EventsServerOptions): http.Server { return; } - options.notificationAPI.getNotification(id) + options.notificationAPI + .getNotification(id) .then((notification) => { if (!notification) { sendErr(res, 404, 'Notification not found', ErrorCode.NOT_FOUND); @@ -1071,7 +1148,10 @@ export function createEventsServer(options: EventsServerOptions): http.Server { return; } - function isValidApiKey(apiKey: string | undefined, allowedKeys: Array<{ key: string; name?: string }> | undefined): boolean { + function isValidApiKey( + apiKey: string | undefined, + allowedKeys: Array<{ key: string; name?: string }> | undefined, + ): boolean { if (!allowedKeys || allowedKeys.length === 0) { // If no API keys are configured, allow unauthenticated access is allowed (for backward compatibility) return true; @@ -1079,7 +1159,7 @@ export function createEventsServer(options: EventsServerOptions): http.Server { if (!apiKey) { return false; } - return allowedKeys.some(k => k.key === apiKey); + return allowedKeys.some((k) => k.key === apiKey); } // Get notification delivery history endpoint @@ -1091,23 +1171,37 @@ export function createEventsServer(options: EventsServerOptions): http.Server { } const url = new URL(req.url, 'http://localhost'); - const limit = url.searchParams.get('limit') ? parseInt(url.searchParams.get('limit')!, 10) : undefined; - const offset = url.searchParams.get('offset') ? parseInt(url.searchParams.get('offset')!, 10) : undefined; + const limit = url.searchParams.get('limit') + ? parseInt(url.searchParams.get('limit')!, 10) + : undefined; + const offset = url.searchParams.get('offset') + ? parseInt(url.searchParams.get('offset')!, 10) + : undefined; const cursor = url.searchParams.get('cursor') || undefined; const status = url.searchParams.get('status') as 'SUCCESS' | 'FAILED' | 'RETRY' | null; const startDate = url.searchParams.get('startDate'); const endDate = url.searchParams.get('endDate'); logger.info('Handling GET /api/notifications/history', { - requestId, correlationId, limit, offset, cursor, status, startDate, endDate, + requestId, + correlationId, + limit, + offset, + cursor, + status, + startDate, + endDate, }); - historyService.getHistory({ - limit, offset, cursor, - status: status || undefined, - startDate: startDate || undefined, - endDate: endDate || undefined, - }) + historyService + .getHistory({ + limit, + offset, + cursor, + status: status || undefined, + startDate: startDate || undefined, + endDate: endDate || undefined, + }) .then((result) => { sendJson(res, 200, result); logger.info('GET /api/notifications/history complete', { @@ -1118,7 +1212,11 @@ export function createEventsServer(options: EventsServerOptions): http.Server { }); }) .catch((error) => { - logger.error('Failed to retrieve notification history', { error, requestId, correlationId }); + logger.error('Failed to retrieve notification history', { + error, + requestId, + correlationId, + }); sendErr(res, 500, (error as Error).message, ErrorCode.INTERNAL_ERROR); }); return; @@ -1134,10 +1232,14 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const type = url.searchParams.get('type') ?? undefined; const startDate = url.searchParams.get('startDate') ?? undefined; const endDate = url.searchParams.get('endDate') ?? undefined; - const limit = url.searchParams.get('limit') ? parseInt(url.searchParams.get('limit')!, 10) : undefined; - const offset = url.searchParams.get('offset') ? parseInt(url.searchParams.get('offset')!, 10) : undefined; + const limit = url.searchParams.get('limit') + ? parseInt(url.searchParams.get('limit')!, 10) + : undefined; + const offset = url.searchParams.get('offset') + ? parseInt(url.searchParams.get('offset')!, 10) + : undefined; const rawSortBy = url.searchParams.get('sortBy') ?? undefined; - const sortBy = (rawSortBy === 'oldest' || rawSortBy === 'status') ? rawSortBy : 'newest'; + const sortBy = rawSortBy === 'oldest' || rawSortBy === 'status' ? rawSortBy : 'newest'; logger.info('Handling GET /api/notifications/search', { requestId, @@ -1155,22 +1257,27 @@ export function createEventsServer(options: EventsServerOptions): http.Server { sortBy, }); - notificationSearchService.search({ - q, - sender, - txHash, - eventId, - status, - type, - startDate, - endDate, - limit, - offset, - sortBy, - }) + notificationSearchService + .search({ + q, + sender, + txHash, + eventId, + status, + type, + startDate, + endDate, + limit, + offset, + sortBy, + }) .then((result) => { sendOk(res, 200, result); - logger.info('GET /api/notifications/search complete', { requestId, total: result.total, durationMs: Date.now() - startTime }); + logger.info('GET /api/notifications/search complete', { + requestId, + total: result.total, + durationMs: Date.now() - startTime, + }); }) .catch((error) => { logger.error('Failed to search notifications', { error, requestId, correlationId }); @@ -1182,11 +1289,14 @@ export function createEventsServer(options: EventsServerOptions): http.Server { // GET /api/search/suggestions if (req.method === 'GET' && url.pathname === '/api/search/suggestions') { const q = url.searchParams.get('q') || ''; - const limit = url.searchParams.get('limit') ? parseInt(url.searchParams.get('limit')!, 10) : undefined; + const limit = url.searchParams.get('limit') + ? parseInt(url.searchParams.get('limit')!, 10) + : undefined; logger.info('Handling GET /api/search/suggestions', { requestId, correlationId, q, limit }); - suggestionService.getSuggestions(q, limit) + suggestionService + .getSuggestions(q, limit) .then((result) => { sendOk(res, 200, result); logger.info('GET /api/search/suggestions complete', { @@ -1196,7 +1306,11 @@ export function createEventsServer(options: EventsServerOptions): http.Server { }); }) .catch((error) => { - logger.error('Failed to retrieve search suggestions', { error, requestId, correlationId }); + logger.error('Failed to retrieve search suggestions', { + error, + requestId, + correlationId, + }); sendErr(res, 500, (error as Error).message, ErrorCode.INTERNAL_ERROR); }); return; @@ -1210,8 +1324,11 @@ export function createEventsServer(options: EventsServerOptions): http.Server { } logger.info('Handling GET /api/templates', { requestId, correlationId }); - (options.templateService as any).listAll() - .then((templates: any[]) => { sendOk(res, 200, templates.map(serializeTemplate)); }) + (options.templateService as any) + .listAll() + .then((templates: any[]) => { + sendOk(res, 200, templates.map(serializeTemplate)); + }) .catch((error: Error) => { logger.error('Failed to list templates', { error, requestId, correlationId }); sendErr(res, 500, error.message, ErrorCode.INTERNAL_ERROR); @@ -1228,9 +1345,14 @@ export function createEventsServer(options: EventsServerOptions): http.Server { } const templateId = decodeURIComponent(templateAuditMatch[1]); - logger.info('Handling GET /api/templates/:id/audit', { requestId, correlationId, templateId }); + logger.info('Handling GET /api/templates/:id/audit', { + requestId, + correlationId, + templateId, + }); - (options.templateService as any).getAuditHistory(templateId) + (options.templateService as any) + .getAuditHistory(templateId) .then(async (records: any[]) => { const template = await (options.templateService as any).getById(templateId); if (!template && records.length === 0) { @@ -1240,7 +1362,12 @@ export function createEventsServer(options: EventsServerOptions): http.Server { sendOk(res, 200, { templateId, records: records.map(serializeAuditRecord) }); }) .catch((error: Error) => { - logger.error('Failed to load template audit history', { error, requestId, correlationId, templateId }); + logger.error('Failed to load template audit history', { + error, + requestId, + correlationId, + templateId, + }); sendErr(res, 500, error.message, ErrorCode.INTERNAL_ERROR); }); return; @@ -1257,7 +1384,8 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const templateId = decodeURIComponent(getTemplateMatch[1]); logger.info('Handling GET /api/templates/:id', { requestId, correlationId, templateId }); - (options.templateService as any).getById(templateId) + (options.templateService as any) + .getById(templateId) .then((template: any) => { if (!template) { sendErr(res, 404, 'Template not found', ErrorCode.NOT_FOUND); @@ -1281,10 +1409,17 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const templateId = decodeURIComponent(getTemplateMatch[1]); const actor = resolveRequestActor(req); - logger.info('Handling PUT /api/templates/:id', { requestId, correlationId, templateId, actor }); + logger.info('Handling PUT /api/templates/:id', { + requestId, + correlationId, + templateId, + actor, + }); let body = ''; - req.on('data', (chunk) => { body += chunk; }); + req.on('data', (chunk) => { + body += chunk; + }); req.on('end', () => { void (async () => { try { @@ -1292,7 +1427,11 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const input = parseTemplateUpdateBody(parsed); const updated = await (options.templateService as any).update(templateId, input, actor); logger.info('PUT /api/templates/:id complete', { - requestId, correlationId, templateId, actor, durationMs: Date.now() - startTime, + requestId, + correlationId, + templateId, + actor, + durationMs: Date.now() - startTime, }); sendOk(res, 200, serializeTemplate(updated)); } catch (error) { @@ -1304,11 +1443,20 @@ export function createEventsServer(options: EventsServerOptions): http.Server { sendErr(res, 404, error.message, ErrorCode.NOT_FOUND); return; } - if (error instanceof TemplateValidationError || (error instanceof Error && error.message.startsWith('Invalid body'))) { + if ( + error instanceof TemplateValidationError || + (error instanceof Error && error.message.startsWith('Invalid body')) + ) { sendErr(res, 400, (error as Error).message, ErrorCode.BAD_REQUEST); return; } - logger.error('Failed to update template', { error, requestId, correlationId, templateId, actor }); + logger.error('Failed to update template', { + error, + requestId, + correlationId, + templateId, + actor, + }); sendErr(res, 500, (error as Error).message, ErrorCode.INTERNAL_ERROR); } })(); @@ -1327,14 +1475,22 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const templateId = decodeURIComponent(deleteTemplateMatch[1]); logger.info('Handling DELETE /api/templates/:id', { requestId, correlationId, templateId }); - (options.templateService as any).delete(templateId) - .then(() => { sendOk(res, 200, { deleted: true }); }) + (options.templateService as any) + .delete(templateId) + .then(() => { + sendOk(res, 200, { deleted: true }); + }) .catch((error: any) => { if (error instanceof TemplateNotFoundError) { sendErr(res, 404, error.message, ErrorCode.NOT_FOUND); return; } - logger.error('Failed to delete template', { error, requestId, correlationId, templateId }); + logger.error('Failed to delete template', { + error, + requestId, + correlationId, + templateId, + }); sendErr(res, 500, (error as Error).message, ErrorCode.INTERNAL_ERROR); }); return; @@ -1349,13 +1505,20 @@ export function createEventsServer(options: EventsServerOptions): http.Server { logger.info('Handling POST /api/templates', { requestId, correlationId }); let body = ''; - req.on('data', (chunk) => { body += chunk; }); + req.on('data', (chunk) => { + body += chunk; + }); req.on('end', () => { void (async () => { try { const parsed = JSON.parse(body) as CreateNotificationTemplateInput; if (!parsed?.id || !parsed?.name || !parsed?.type || !parsed?.body) { - sendErr(res, 400, 'Invalid body: id, name, type, and body are required', ErrorCode.BAD_REQUEST); + sendErr( + res, + 400, + 'Invalid body: id, name, type, and body are required', + ErrorCode.BAD_REQUEST, + ); return; } @@ -1387,14 +1550,20 @@ export function createEventsServer(options: EventsServerOptions): http.Server { } const templateId = decodeURIComponent(templateRenderMatch[1]); - logger.info('Handling POST /api/templates/:id/render', { requestId, correlationId, templateId }); + logger.info('Handling POST /api/templates/:id/render', { + requestId, + correlationId, + templateId, + }); let body = ''; - req.on('data', (chunk) => { body += chunk; }); + req.on('data', (chunk) => { + body += chunk; + }); req.on('end', () => { void (async () => { try { - const parsed = body ? JSON.parse(body) as Record : {}; + const parsed = body ? (JSON.parse(body) as Record) : {}; const template = await (options.templateService as any).getById(templateId); if (!template) { sendErr(res, 404, `Template not found: ${templateId}`, ErrorCode.NOT_FOUND); @@ -1411,7 +1580,12 @@ export function createEventsServer(options: EventsServerOptions): http.Server { sendErr(res, 422, error.message, ErrorCode.UNPROCESSABLE); return; } - logger.error('Failed to render template', { error, requestId, correlationId, templateId }); + logger.error('Failed to render template', { + error, + requestId, + correlationId, + templateId, + }); sendErr(res, 500, (error as Error).message, ErrorCode.INTERNAL_ERROR); } })(); @@ -1435,20 +1609,40 @@ export function createEventsServer(options: EventsServerOptions): http.Server { const userId = decodeURIComponent(putPrefsMatch[1]); logger.info('Handling PUT /api/preferences/:userId', { requestId, correlationId, userId }); let body = ''; - req.on('data', (chunk) => { body += chunk; }); + req.on('data', (chunk) => { + body += chunk; + }); req.on('end', () => { try { const input: PreferencesUpdateInput = JSON.parse(body); if (!input || typeof input.categories !== 'object') { - logger.warn('PUT /api/preferences/:userId invalid body', { requestId, correlationId, userId }); - sendErr(res, 400, 'Invalid body: expected { categories: { [key]: boolean } }', ErrorCode.BAD_REQUEST); + logger.warn('PUT /api/preferences/:userId invalid body', { + requestId, + correlationId, + userId, + }); + sendErr( + res, + 400, + 'Invalid body: expected { categories: { [key]: boolean } }', + ErrorCode.BAD_REQUEST, + ); return; } const updated = preferenceStore.update(userId, input); - logger.info('PUT /api/preferences/:userId complete', { requestId, correlationId, userId, durationMs: Date.now() - startTime }); + logger.info('PUT /api/preferences/:userId complete', { + requestId, + correlationId, + userId, + durationMs: Date.now() - startTime, + }); sendOk(res, 200, updated); } catch { - logger.error('PUT /api/preferences/:userId invalid JSON', { requestId, correlationId, userId }); + logger.error('PUT /api/preferences/:userId invalid JSON', { + requestId, + correlationId, + userId, + }); sendErr(res, 400, 'Invalid JSON', ErrorCode.PARSE_ERROR); } }); @@ -1456,11 +1650,19 @@ export function createEventsServer(options: EventsServerOptions): http.Server { } // GET /api/archive, GET /api/archive/:id, POST /api/archive/run - if (options.archiveStore && (url.pathname === '/api/archive' || url.pathname.startsWith('/api/archive/'))) { - const handled = await handleArchiveRequest(req, res, { - store: options.archiveStore, - service: options.archiveService, - }, requestId); + if ( + options.archiveStore && + (url.pathname === '/api/archive' || url.pathname.startsWith('/api/archive/')) + ) { + const handled = await handleArchiveRequest( + req, + res, + { + store: options.archiveStore, + service: options.archiveService, + }, + requestId, + ); if (handled) return; } @@ -1477,26 +1679,26 @@ export function createEventsServer(options: EventsServerOptions): http.Server { ); if (deliveryMetricsHandled) return; -// GET /api/metrics/response-time — expose response-time counters (#491) - if (req.method === 'GET' && url.pathname === '/api/metrics/response-time') { - const metrics = responseTime.getMetrics(); - const reset = url.searchParams.get('reset') === 'true'; - sendOk(res, 200, metrics); - if (reset) { - responseTime.resetMetrics(); - logger.info('Response-time metrics reset', { requestId }); - } - return; - } - - logger.warn('Unhandled request', { - requestId, - correlationId, - method: req.method, - url: req.url, - }); - sendErr(res, 404, 'Not found', ErrorCode.NOT_FOUND); - responseTime.finish(req, res, requestId, 404); + // GET /api/metrics/response-time — expose response-time counters (#491) + if (req.method === 'GET' && url.pathname === '/api/metrics/response-time') { + const metrics = responseTime.getMetrics(); + const reset = url.searchParams.get('reset') === 'true'; + sendOk(res, 200, metrics); + if (reset) { + responseTime.resetMetrics(); + logger.info('Response-time metrics reset', { requestId }); + } + return; + } + + logger.warn('Unhandled request', { + requestId, + correlationId, + method: req.method, + url: req.url, + }); + sendErr(res, 404, 'Not found', ErrorCode.NOT_FOUND); + responseTime.finish(req, res, requestId, 404); }); if (rateLimiter) { diff --git a/listener/src/config.ts b/listener/src/config.ts index 3dbdd3f6..2fce1ca9 100644 --- a/listener/src/config.ts +++ b/listener/src/config.ts @@ -1,7 +1,20 @@ -import { Config, ContractConfig, DiscordConfig, WebhookSecret, AppCleanupConfig, EventQueueConfig, RetrySchedulerOptions, AnalyticsConfig, ExpirationConfig, ApiKey } from './types'; import { validateCorsOrigin, CorsValidationError } from './utils/cors-validator'; import { ConfigurationSchemaValidator, APP_CONFIG_SCHEMA } from './config-schema'; -import { Config, ContractConfig, DiscordConfig, WebhookSecret, AppCleanupConfig, EventQueueConfig, RetrySchedulerOptions, AnalyticsConfig, ExpirationConfig, ApiKey, BackfillConfig, LoggingConfig, ApiConfig } from './types'; +import { + Config, + ContractConfig, + DiscordConfig, + WebhookSecret, + AppCleanupConfig, + EventQueueConfig, + RetrySchedulerOptions, + AnalyticsConfig, + ExpirationConfig, + ApiKey, + BackfillConfig, + LoggingConfig, + ApiConfig, +} from './types'; import { SUPPORTED_LOG_FORMATS, SUPPORTED_LOG_LEVELS, @@ -33,7 +46,7 @@ function validateRequiredEnvVars(): void { if (missing.length > 0) { throw new ConfigError( `Missing required environment variable(s): ${missing.join(', ')}. ` + - 'Copy .env.example to .env and set them before starting the listener.' + 'Copy .env.example to .env and set them before starting the listener.', ); } } @@ -64,7 +77,9 @@ function validateContractAddresses(value: unknown): ContractConfig[] { return value.map((item, index) => { if (typeof item !== 'object' || item === null) { - throw new ConfigError(`CONTRACT_ADDRESSES[${index}] must be an object with address and events.`); + throw new ConfigError( + `CONTRACT_ADDRESSES[${index}] must be an object with address and events.`, + ); } const address = (item as any).address; @@ -76,7 +91,7 @@ function validateContractAddresses(value: unknown): ContractConfig[] { if (!Array.isArray(events) || events.some((event) => typeof event !== 'string')) { throw new ConfigError( - `CONTRACT_ADDRESSES[${index}].events must be an array of string event names.` + `CONTRACT_ADDRESSES[${index}].events must be an array of string event names.`, ); } @@ -118,9 +133,7 @@ function validateWebhookSecrets(value: unknown): WebhookSecret[] { return value.map((item, index) => { if (typeof item !== 'object' || item === null) { - throw new ConfigError( - `WEBHOOK_SECRETS[${index}] must be an object with id and secret.` - ); + throw new ConfigError(`WEBHOOK_SECRETS[${index}] must be an object with id and secret.`); } const id = (item as any).id; @@ -145,9 +158,7 @@ function validateApiKeys(value: unknown): ApiKey[] { return value.map((item, index) => { if (typeof item !== 'object' || item === null) { - throw new ConfigError( - `API_KEYS[${index}] must be an object with key (and optional name).` - ); + throw new ConfigError(`API_KEYS[${index}] must be an object with key (and optional name).`); } const key = (item as any).key; @@ -164,8 +175,14 @@ function validateApiKeys(value: unknown): ApiKey[] { function loadCleanupConfig(): AppCleanupConfig { return { intervalMs: parseIntegerEnv('CLEANUP_INTERVAL_MS', String(60 * 60 * 1000)), - notificationRetentionMs: parseIntegerEnv('NOTIFICATION_RETENTION_MS', String(7 * 24 * 60 * 60 * 1000)), - rateLimitEventRetentionMs: parseIntegerEnv('RATE_LIMIT_EVENT_RETENTION_MS', String(24 * 60 * 60 * 1000)), + notificationRetentionMs: parseIntegerEnv( + 'NOTIFICATION_RETENTION_MS', + String(7 * 24 * 60 * 60 * 1000), + ), + rateLimitEventRetentionMs: parseIntegerEnv( + 'RATE_LIMIT_EVENT_RETENTION_MS', + String(24 * 60 * 60 * 1000), + ), eventRetentionMs: parseIntegerEnv('EVENT_RETENTION_MS', String(24 * 60 * 60 * 1000)), processedEventRetentionMs: parseIntegerEnv( 'PROCESSED_EVENT_RETENTION_MS', @@ -207,21 +224,27 @@ function loadExpirationConfig(): ExpirationConfig { const defaultExpirationMs = parseIntegerEnv('EXPIRATION_DEFAULT_MS', String(24 * 60 * 60 * 1000)); const perEventTypeExpirationJson = trimEnv('EXPIRATION_PER_EVENT_TYPE'); let perEventTypeExpiration: Record | undefined; - + if (perEventTypeExpirationJson) { try { perEventTypeExpiration = JSON.parse(perEventTypeExpirationJson); - if (typeof perEventTypeExpiration !== 'object' || perEventTypeExpiration === null || Array.isArray(perEventTypeExpiration)) { + if ( + typeof perEventTypeExpiration !== 'object' || + perEventTypeExpiration === null || + Array.isArray(perEventTypeExpiration) + ) { throw new ConfigError('EXPIRATION_PER_EVENT_TYPE must be a valid JSON object'); } } catch (e) { if (e instanceof ConfigError) { throw e; } - throw new ConfigError(`EXPIRATION_PER_EVENT_TYPE must be valid JSON. Received: ${perEventTypeExpirationJson}`); + throw new ConfigError( + `EXPIRATION_PER_EVENT_TYPE must be valid JSON. Received: ${perEventTypeExpirationJson}`, + ); } } - + return { defaultExpirationMs, perEventTypeExpiration, @@ -253,14 +276,14 @@ export function loadConfig(): Config { const rawApiKeys = parseJsonEnv('API_KEYS', '[]'); const clientOverrides = parseJsonEnv>( 'RATE_LIMIT_CLIENT_OVERRIDES', - '{}' + '{}', ); return { stellarNetwork: trimEnv('STELLAR_NETWORK') || 'testnet', - stellarRpcUrl: - trimEnv('STELLAR_RPC_URL') || 'https://soroban-testnet.stellar.org:443', - stellarNetworkPassphrase: trimEnv('STELLAR_NETWORK_PASSPHRASE') || 'Test SDF Network ; September 2015', + stellarRpcUrl: trimEnv('STELLAR_RPC_URL') || 'https://soroban-testnet.stellar.org:443', + stellarNetworkPassphrase: + trimEnv('STELLAR_NETWORK_PASSPHRASE') || 'Test SDF Network ; September 2015', contractAddresses: validateContractAddresses(rawContractAddresses), pollIntervalMs: parseIntegerEnv('POLL_INTERVAL_MS', '30000'), eventBatchSize: parseIntegerEnv('EVENT_BATCH_SIZE', '100'), @@ -321,9 +344,7 @@ function loadLoggingConfig(): LoggingConfig { level: trimEnv('LOG_LEVEL') || 'info', // Preserves the previous implicit behaviour when LOG_FORMAT is unset: // JSON in production, human-readable elsewhere. - format: - trimEnv('LOG_FORMAT') || - (process.env.NODE_ENV === 'production' ? 'json' : 'pretty'), + format: trimEnv('LOG_FORMAT') || (process.env.NODE_ENV === 'production' ? 'json' : 'pretty'), }; } @@ -358,9 +379,7 @@ export function validateConfig(config: Config): void { ); } } catch { - errors.push( - `STELLAR_RPC_URL is not a valid URL (received: "${config.stellarRpcUrl}").`, - ); + errors.push(`STELLAR_RPC_URL is not a valid URL (received: "${config.stellarRpcUrl}").`); } } @@ -404,22 +423,16 @@ export function validateConfig(config: Config): void { } if (config.maxReconnectAttempts < 1) { - errors.push( - `MAX_RECONNECT_ATTEMPTS must be >= 1 (received: ${config.maxReconnectAttempts}).`, - ); + errors.push(`MAX_RECONNECT_ATTEMPTS must be >= 1 (received: ${config.maxReconnectAttempts}).`); } if (config.reconnectDelayMs < 0) { - errors.push( - `RECONNECT_DELAY_MS must be >= 0 (received: ${config.reconnectDelayMs}).`, - ); + errors.push(`RECONNECT_DELAY_MS must be >= 0 (received: ${config.reconnectDelayMs}).`); } // ── API server ───────────────────────────────────────────────────────────── if (config.eventsApiPort < 1 || config.eventsApiPort > 65535) { - errors.push( - `EVENTS_API_PORT must be between 1 and 65535 (received: ${config.eventsApiPort}).`, - ); + errors.push(`EVENTS_API_PORT must be between 1 and 65535 (received: ${config.eventsApiPort}).`); } // Validate CORS configuration during startup (#689) @@ -448,7 +461,7 @@ export function validateConfig(config: Config): void { 'Add contract configurations or the service will not process any events.', ); } - + config.contractAddresses.forEach((contract, index) => { if (!contract.address || typeof contract.address !== 'string') { errors.push(`CONTRACT_ADDRESSES[${index}].address must be a non-empty string.`); @@ -468,7 +481,7 @@ export function validateConfig(config: Config): void { ); } } - + if (!Array.isArray(contract.events) || contract.events.length === 0) { errors.push( `CONTRACT_ADDRESSES[${index}].events must be a non-empty array of event names.`, @@ -577,9 +590,7 @@ export function validateConfig(config: Config): void { // ── Analytics ───────────────────────────────────────────────────────────── if (config.analytics) { if (config.analytics.maxRecords < 1) { - errors.push( - `ANALYTICS_MAX_RECORDS must be >= 1 (received: ${config.analytics.maxRecords}).`, - ); + errors.push(`ANALYTICS_MAX_RECORDS must be >= 1 (received: ${config.analytics.maxRecords}).`); } if (config.analytics.bucketSizeMs < 60_000) { errors.push( @@ -688,17 +699,16 @@ export function validateConfig(config: Config): void { required: false, }, // Webhook signing secrets - ...((config.webhookSecrets ?? []).map((ws, i) => ({ + ...(config.webhookSecrets ?? []).map((ws, i) => ({ fieldName: `WEBHOOK_SECRETS[${i}].secret`, value: ws.secret, required: true, - }))), + })), // API keys - ...((config.apiKeys ?? []).map((ak, i) => ({ + ...(config.apiKeys ?? []).map((ak, i) => ({ fieldName: `API_KEYS[${i}].key`, value: ak.key, required: true, - }))), + })), ]); } - diff --git a/listener/src/index.ts b/listener/src/index.ts index 169ab237..ba04ec61 100644 --- a/listener/src/index.ts +++ b/listener/src/index.ts @@ -67,19 +67,13 @@ async function main() { const db = await initializeDatabase(config.databasePath); repository = new ScheduledNotificationRepository(db); - + healthMonitor = new NotificationHealthMonitor(null, getWorkerManager(), { repository, getLastSuccessfulPoll: () => subscriber?.getLastSuccessfulPoll() ?? null, - }); - getUptimeMs: () => Date.now() - PROCESS_START_TIME, }); - healthMonitor = new NotificationHealthMonitor(null, getWorkerManager(), { - repository, - }); - // Rebuild registry with configured event TTL if (config.cleanup) { eventRegistry.setTtlMs(config.cleanup.eventRetentionMs); @@ -171,8 +165,7 @@ async function main() { healthMonitor.start(); } - subscriber = new EventSubscriber(config, deduplicationService); - const subscriber = new EventSubscriber(config, deduplicationService ?? undefined); + subscriber = new EventSubscriber(config, deduplicationService ?? undefined); await subscriber.start(); let isShuttingDown = false; @@ -216,11 +209,11 @@ async function main() { await retryScheduler.stop(); } - if (subscriber) { - await subscriber.stop(); - } + if (subscriber) { + await subscriber.stop(); + } - eventsServer.close(); + eventsServer.close(); logger.info('Graceful shutdown completed successfully', { signal }); process.exit(0); @@ -252,4 +245,4 @@ main().catch((err) => { logger.error('Error starting service', { error: err }); } process.exit(1); -}); \ No newline at end of file +}); diff --git a/listener/src/middleware/security-headers.ts b/listener/src/middleware/security-headers.ts index d46499ff..37703d3b 100644 --- a/listener/src/middleware/security-headers.ts +++ b/listener/src/middleware/security-headers.ts @@ -10,13 +10,13 @@ * See: https://owasp.org/www-project-secure-headers/ */ -import type { http.ServerResponse } from 'http'; +import type { ServerResponse } from 'http'; const isLocalhost = (hostname: string): boolean => hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1'; export function addSecurityHeaders( - res: http.ServerResponse, + res: ServerResponse, options: { productionOrigin?: string } = {}, ): void { const origin = res.getHeader('Access-Control-Allow-Origin') as string | undefined; @@ -31,9 +31,7 @@ export function addSecurityHeaders( // ✅ X-Frame-Options: prevent clickjacking. // SAMEORIGIN allows embedding within same-origin iframes (dashboard use). // DENY would break legitimate self-embedding; SAMEORIGIN is safer. - const frameOption = options.productionOrigin - ? 'SAMEORIGIN' - : 'SAMEORIGIN'; + const frameOption = options.productionOrigin ? 'SAMEORIGIN' : 'SAMEORIGIN'; res.setHeader('X-Frame-Options', frameOption); // ✅ X-XSS-Protection: legacy IE protection (defense-in-depth). @@ -55,9 +53,6 @@ export function addSecurityHeaders( // ⚠️ Strict-Transport-Security: only in production with a valid origin. // Skipped for localhost/development to avoid breaking HTTP local testing. if (options.productionOrigin && !isLocalhost(origin || '')) { - res.setHeader( - 'Strict-Transport-Security', - 'max-age=31536000; includeSubDomains; preload', - ); + res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); } -} \ No newline at end of file +} diff --git a/listener/src/services/discord-notification.ts b/listener/src/services/discord-notification.ts index 47b79249..b796a5a4 100644 --- a/listener/src/services/discord-notification.ts +++ b/listener/src/services/discord-notification.ts @@ -3,7 +3,10 @@ import logger from '../utils/logger'; import { ContractConfig, DiscordConfig } from '../types'; import { getEventName } from '../utils/event-utils'; import { NotificationDeduplicator, generateFingerprint } from './notification-deduplicator'; -import { getNotificationAnalyticsAggregator, NotificationAnalyticsAggregator } from './notification-analytics-aggregator'; +import { + getNotificationAnalyticsAggregator, + NotificationAnalyticsAggregator, +} from './notification-analytics-aggregator'; import { sendWebhook } from './webhook-sender'; import { NotificationType } from '../types/scheduled-notification'; import { generateCorrelationId } from '../utils/request-id'; @@ -55,9 +58,10 @@ const MARKDOWN_CHARS = /([*`~|\\])/g; * static strings (embed titles, field labels) don't require it. */ export function sanitizeForDiscord(text: string): string { - return text - .replace(MENTION_PATTERN, '[mention removed]') - .replace(MARKDOWN_CHARS, '\\$1'); + return text.replace(MENTION_PATTERN, '[mention removed]').replace(MARKDOWN_CHARS, '\\$1'); +} + +// --------------------------------------------------------------------------- // Internal helpers // --------------------------------------------------------------------------- @@ -107,7 +111,7 @@ export class DiscordNotificationService { async sendEventNotification( event: StellarSDK.rpc.Api.EventResponse, contractConfig: ContractConfig, - requestId?: string + requestId?: string, ): Promise { const correlationId = requestId ?? generateCorrelationId(); const fingerprint = generateFingerprint(event.id, contractConfig.address); @@ -181,7 +185,9 @@ export class DiscordNotificationService { ...logContext, httpStatus: response.status, httpCategory: responseCategory, - ...(responseCategory === 'rate_limited' && { retryAfter: response.headers?.get('retry-after') }), + ...(responseCategory === 'rate_limited' && { + retryAfter: response.headers?.get('retry-after'), + }), errorSummary: errorBody, durationMs, attempt, @@ -278,7 +284,10 @@ export class DiscordNotificationService { return new Promise((resolve) => setTimeout(resolve, ms)); } - private async sendWebhook(message: DiscordMessage, logContext?: Record): Promise { + private async sendWebhook( + message: DiscordMessage, + logContext?: Record, + ): Promise { try { const response = await sendWebhook(this.config.webhookUrl, message, { timeoutMs: this.config.timeoutMs, @@ -299,7 +308,7 @@ export class DiscordNotificationService { private formatEventMessage( event: StellarSDK.rpc.Api.EventResponse, - contractConfig: ContractConfig + contractConfig: ContractConfig, ): DiscordMessage { const eventName = sanitizeForDiscord(getEventName(event.topic) ?? 'Unknown Event'); const embed = this.createEventEmbed(event, contractConfig, eventName); @@ -313,7 +322,7 @@ export class DiscordNotificationService { private createEventEmbed( event: StellarSDK.rpc.Api.EventResponse, contractConfig: ContractConfig, - eventName: string + eventName: string, ): DiscordEmbed { const fields: { name: string; value: string; inline?: boolean }[] = [ { @@ -382,7 +391,7 @@ export class DiscordNotificationService { }); } - const fields = embed.fields?.map(field => { + const fields = embed.fields?.map((field) => { let value = field.value; if (value.length > MAX_DISCORD_FIELD_VALUE_LENGTH) { value = value.slice(0, MAX_DISCORD_FIELD_VALUE_LENGTH - 3) + '...'; @@ -409,9 +418,14 @@ export class DiscordNotificationService { const totalLength = this.getEmbedLength(sanitized); if (totalLength > MAX_DISCORD_EMBED_LENGTH) { const excess = totalLength - MAX_DISCORD_EMBED_LENGTH; - const valueFieldIndex = sanitized.fields?.findIndex(f => f.name === 'Value'); - - if (valueFieldIndex !== undefined && valueFieldIndex >= 0 && sanitized.fields && sanitized.fields[valueFieldIndex]) { + const valueFieldIndex = sanitized.fields?.findIndex((f) => f.name === 'Value'); + + if ( + valueFieldIndex !== undefined && + valueFieldIndex >= 0 && + sanitized.fields && + sanitized.fields[valueFieldIndex] + ) { const currentValue = sanitized.fields[valueFieldIndex].value; const newValueLength = Math.max(0, currentValue.length - excess); const newValue = @@ -422,7 +436,7 @@ export class DiscordNotificationService { sanitized = { ...sanitized, fields: sanitized.fields.map((f, i) => - i === valueFieldIndex ? { ...f, value: newValue } : f + i === valueFieldIndex ? { ...f, value: newValue } : f, ), }; @@ -452,7 +466,9 @@ export class DiscordNotificationService { return String(value.i64()); case StellarSDK.xdr.ScValType.scvString(): { const strVal = value.str().toString(); - return strVal.length > MAX_DISCORD_FIELD_VALUE_LENGTH ? strVal.slice(0, MAX_DISCORD_FIELD_VALUE_LENGTH) + '...' : strVal; + return strVal.length > MAX_DISCORD_FIELD_VALUE_LENGTH + ? strVal.slice(0, MAX_DISCORD_FIELD_VALUE_LENGTH) + '...' + : strVal; const truncated = strVal.length > 500 ? strVal.slice(0, 500) + '...' : strVal; return sanitizeForDiscord(truncated); } @@ -468,4 +484,3 @@ export class DiscordNotificationService { } } } - diff --git a/listener/src/services/event-subscriber.ts b/listener/src/services/event-subscriber.ts index 82dcda7c..36eea0ea 100644 --- a/listener/src/services/event-subscriber.ts +++ b/listener/src/services/event-subscriber.ts @@ -29,30 +29,31 @@ export class EventSubscriber { private eventQueue: EventProcessingQueue | null = null; private expirationService: NotificationExpirationService | null = null; private lastSuccessfulPollAt: number | null = null; + /** Cached backfill floor (ledger number) resolved once per subscriber session (#backfill safety limit). */ + private backfillStartLedger: number | null = null; constructor(config: Config, deduplicationService?: EventDeduplicationService) { this.config = config; this.server = new StellarSDK.rpc.Server(config.stellarRpcUrl); this.deduplicationService = deduplicationService ?? null; - + // Initialize expiration service if configured if (config.expiration) { this.expirationService = new NotificationExpirationService(config.expiration); } - + if (config.discord) { this.discordService = new DiscordNotificationService(config.discord); this.retryQueue = new NotificationRetryQueue( (event, contractConfig, requestId) => this.discordService!.sendEventNotification(event, contractConfig, requestId), - config.retryQueue + config.retryQueue, ); } if (config.eventQueue) { this.eventQueue = new EventProcessingQueue( - (event, contractConfig, requestId) => - this.processEvent(event, contractConfig, requestId), - config.eventQueue + (event, contractConfig, requestId) => this.processEvent(event, contractConfig, requestId), + config.eventQueue, ); } } @@ -129,14 +130,14 @@ export class EventSubscriber { } const events = response.events || []; - + // Detect potential reorg if events exist and we have previous state if (this.deduplicationService && events.length > 0) { const firstEventLedger = events[0]?.ledger; if (firstEventLedger) { const reorgDetected = await this.deduplicationService.detectReorg( contractConfig.address, - firstEventLedger + firstEventLedger, ); if (reorgDetected) { logger.warn('Potential blockchain reorg detected', { @@ -170,10 +171,6 @@ export class EventSubscriber { }); } } - const processableEvents = events.filter((event: StellarSDK.rpc.Api.EventResponse) => - this.shouldProcessEvent(event, contractConfig, requestId) - ); - if (events.length > 0) { logger.info('Received events', { requestId, @@ -206,14 +203,14 @@ export class EventSubscriber { if (response.cursor) { this.lastCursors.set(contractConfig.address, response.cursor); - + // Update cursor in deduplication service if available if (this.deduplicationService) { const lastEventLedger = events.length > 0 ? events[events.length - 1].ledger : 0; await this.deduplicationService.updatePollingCursor( contractConfig.address, response.cursor, - lastEventLedger || 0 + lastEventLedger || 0, ); } } @@ -228,9 +225,7 @@ export class EventSubscriber { } if (totalContracts > 0 && failureCount === totalContracts) { - throw new Error( - `Failed to fetch events for all ${totalContracts} configured contract(s)` - ); + throw new Error(`Failed to fetch events for all ${totalContracts} configured contract(s)`); } } @@ -238,7 +233,7 @@ export class EventSubscriber { event: StellarSDK.rpc.Api.EventResponse, contractConfig: ContractConfig, requestId: string = '', - correlationId: string = requestId + correlationId: string = requestId, ): boolean { // Check if event has expired if (this.expirationService && !this.expirationService.shouldProcess(event)) { @@ -336,30 +331,9 @@ export class EventSubscriber { } private async getContractEvents( - contractConfig: ContractConfig + contractConfig: ContractConfig, ): Promise { const lastCursor = this.lastCursors.get(contractConfig.address); - const request: StellarSDK.rpc.Api.GetEventsRequest = lastCursor - ? { - filters: [ - { - contractIds: [contractConfig.address], - type: 'contract', - }, - ], - cursor: lastCursor, - limit: this.config.eventBatchSize, - } - : { - filters: [ - { - contractIds: [contractConfig.address], - type: 'contract', - }, - ], - startLedger: 1, - limit: this.config.eventBatchSize, - }; let request: StellarSDK.rpc.Api.GetEventsRequest; @@ -387,14 +361,17 @@ export class EventSubscriber { event: StellarSDK.rpc.Api.EventResponse, contractConfig: ContractConfig, requestId: string = '', - correlationId: string = '' + correlationId: string = '', ): Promise { const eventStart = Date.now(); const eventName = getEventName(event.topic); // Check persistent deduplication first (to catch reorg duplicates) if (this.deduplicationService) { - const duplicate = await this.deduplicationService.isDuplicate(event.id, contractConfig.address); + const duplicate = await this.deduplicationService.isDuplicate( + event.id, + contractConfig.address, + ); if (duplicate.isDuplicate) { logger.warn('Skipping event: already processed (persistent deduplication)', { requestId: correlationId, @@ -403,7 +380,7 @@ export class EventSubscriber { contractAddress: contractConfig.address, isReorgDuplicate: duplicate.isReorgDuplicate, }); - + // Record that we detected this duplicate await this.deduplicationService.recordProcessedEvent( event.id, @@ -412,9 +389,9 @@ export class EventSubscriber { event.txHash, event.type, false, // No notification sent - 'SKIPPED' + 'SKIPPED', ); - + return true; } } @@ -458,7 +435,7 @@ export class EventSubscriber { const success = await this.discordService.sendEventNotification( event, contractConfig, - requestId + requestId, ); notificationSent = success; @@ -474,8 +451,8 @@ export class EventSubscriber { } catch (error) { processingError = error instanceof Error ? error.message : String(error); logger.error('Error sending Discord notification', { - requestId: correlationId, - correlationId, + requestId: correlationId, + correlationId, eventId: event.id, error: processingError, }); @@ -493,7 +470,7 @@ export class EventSubscriber { event.type, notificationSent, processingError ? 'ERROR' : 'PROCESSED', - processingError + processingError, ); } @@ -543,4 +520,4 @@ export class EventSubscriber { getLastSuccessfulPoll(): number | null { return this.lastSuccessfulPollAt; } -} \ No newline at end of file +} diff --git a/listener/src/utils/request-id.ts b/listener/src/utils/request-id.ts index 0a9c61f7..ef670efc 100644 --- a/listener/src/utils/request-id.ts +++ b/listener/src/utils/request-id.ts @@ -14,6 +14,9 @@ export function generateRequestId(): string { */ export function generateCorrelationId(): string { return randomUUID(); +} + +/** * Client-supplied request IDs must be printable ASCII tokens of bounded length. * Rejects empty values, control characters, whitespace, and oversized strings * so untrusted header content is never reused as a log/trace key (#686). From 1ac7be63d302fdd3d56ced6b96ea7378acf0e9f7 Mon Sep 17 00:00:00 2001 From: oluwaseyi1996-netizen Date: Tue, 29 Sep 2026 20:26:43 +0000 Subject: [PATCH 2/4] test(listener): cover API rate limits under normal, burst and repeated load (#852) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the scenario suite the issue asks for, driving both the limiter directly and the real HTTP server: - normal load: every distinct client is admitted, remaining quota strictly decreases, and one client's usage never consumes another's - burst: a concurrent spike is capped at exactly `maxRequests`, every rejection returns the identical 429 envelope, and concurrent distinct clients are all admitted - repeated load across windows: with fake timers, clients are re-admitted once the window rolls over and no quota leaks between windows - overrides / disabled limiter / client isolation - end-to-end over `createEventsServer`: enforcement, no false positives, and /health + /api/rate-limit/metrics staying reachable while throttled Also updates one stale assertion in rate-limiter.test.ts that still expected a bare `Too Many Requests` body; the limiter returns the standard `{success:false,error:{code:'RATE_LIMITED'}}` envelope. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- listener/src/api/rate-limit-scenarios.test.ts | 459 ++++++++++++++++++ listener/src/api/rate-limiter.test.ts | 81 ++-- 2 files changed, 509 insertions(+), 31 deletions(-) create mode 100644 listener/src/api/rate-limit-scenarios.test.ts diff --git a/listener/src/api/rate-limit-scenarios.test.ts b/listener/src/api/rate-limit-scenarios.test.ts new file mode 100644 index 00000000..b337132a --- /dev/null +++ b/listener/src/api/rate-limit-scenarios.test.ts @@ -0,0 +1,459 @@ +/** + * API rate-limit scenario tests (issue #852) + * + * Verifies that the rate limiter behaves predictably under the three traffic + * conditions called out in the issue: + * + * - normal – legitimate clients stay under their quota and are never blocked + * - burst – a sudden spike is capped at exactly `maxRequests` + * - repeated – sustained traffic is re-admitted once the window rolls over + * + * plus two cross-cutting guarantees: + * + * - the 429 response is a stable, machine-readable envelope + * - one client exhausting its quota never affects another client, and the + * observability routes stay reachable while throttled + * + * The unit-level cases drive `RateLimiter.handle` directly with fake + * request/response objects; the final integration block exercises the same + * behaviour end-to-end through a real `http.Server`. + */ + +import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals'; +import http from 'http'; + +import { RateLimiter } from './rate-limiter'; +import { createEventsServer } from './events-server'; +import type { RateLimitConfig } from '../types'; + +jest.mock('../utils/logger', () => ({ + __esModule: true, + default: { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +interface CapturedResponse { + status: number; + headers: Record; + body: any; +} + +interface MockResponse { + res: http.ServerResponse; + captured: () => CapturedResponse; +} + +function makeRequest( + headers: Record = {}, + ip = '127.0.0.1', + url = '/api/events', + method = 'GET', +): http.IncomingMessage { + return { + headers, + socket: { remoteAddress: ip }, + url, + method, + } as unknown as http.IncomingMessage; +} + +/** Minimal ServerResponse stand-in that records the full wire response. */ +function makeResponse(): MockResponse { + const headers: Record = {}; + let status = 200; + let rawBody = ''; + + const res = { + setHeader: (name: string, value: unknown) => { + headers[name.toLowerCase()] = String(value); + }, + getHeader: (name: string) => headers[name.toLowerCase()], + writeHead: (code: number, extra?: Record) => { + status = code; + if (extra) { + for (const [key, value] of Object.entries(extra)) { + headers[key.toLowerCase()] = String(value); + } + } + }, + end: (chunk?: unknown) => { + if (chunk !== undefined) rawBody = String(chunk); + }, + } as unknown as http.ServerResponse; + + return { + res, + captured: () => ({ + status, + headers, + body: rawBody ? JSON.parse(rawBody) : undefined, + }), + }; +} + +const baseConfig = (overrides: Partial = {}): RateLimitConfig => ({ + enabled: true, + windowMs: 60_000, + maxRequests: 5, + clientOverrides: {}, + ...overrides, +}); + +describe('Rate limiting under normal load (#852)', () => { + let limiter: RateLimiter; + + afterEach(() => limiter?.destroy()); + + it('admits every request from distinct clients that stay under quota', async () => { + limiter = new RateLimiter(baseConfig({ maxRequests: 5 })); + + // Five independent API keys, each making four requests (< 5 quota). + const results: boolean[] = []; + for (let client = 0; client < 5; client++) { + const req = makeRequest({ 'x-api-key': `client-${client}` }); + for (let i = 0; i < 4; i++) { + results.push(await limiter.handle(req, makeResponse().res)); + } + } + + expect(results.every(Boolean)).toBe(true); + expect(results).toHaveLength(20); + + const metrics = limiter.getMetrics(); + expect(metrics.totalRequests).toBe(20); + expect(metrics.allowedRequests).toBe(20); + expect(metrics.blockedRequests).toBe(0); + expect(metrics.uniqueClients).toBe(5); + }); + + it('reports a decreasing remaining quota without ever going negative', async () => { + limiter = new RateLimiter(baseConfig({ maxRequests: 3 })); + const req = makeRequest({ 'x-api-key': 'normal-client' }); + + const remaining: string[] = []; + for (let i = 0; i < 3; i++) { + const { res, captured } = makeResponse(); + await limiter.handle(req, res); + remaining.push(captured().headers['x-ratelimit-remaining']); + } + + expect(remaining).toEqual(['2', '1', '0']); + expect(remaining.every((value) => Number(value) >= 0)).toBe(true); + }); + + it('does not block a client merely because others are active', async () => { + limiter = new RateLimiter(baseConfig({ maxRequests: 2 })); + + const chatty = makeRequest({ 'x-api-key': 'chatty' }); + await limiter.handle(chatty, makeResponse().res); + await limiter.handle(chatty, makeResponse().res); + expect(await limiter.handle(chatty, makeResponse().res)).toBe(false); + + // A separate client still has its full quota available. + const quiet = makeRequest({ 'x-api-key': 'quiet' }); + expect(await limiter.handle(quiet, makeResponse().res)).toBe(true); + expect(await limiter.handle(quiet, makeResponse().res)).toBe(true); + }); +}); + +describe('Rate limiting under burst load (#852)', () => { + let limiter: RateLimiter; + + afterEach(() => limiter?.destroy()); + + it('caps a concurrent burst at exactly maxRequests', async () => { + const maxRequests = 5; + const burstSize = 25; + limiter = new RateLimiter(baseConfig({ maxRequests, windowMs: 60_000 })); + + const req = makeRequest({ 'x-api-key': 'burst-client' }); + const responses = await Promise.all( + Array.from({ length: burstSize }, () => limiter.handle(req, makeResponse().res)), + ); + + const allowed = responses.filter(Boolean).length; + const blocked = responses.filter((allowed) => !allowed).length; + + expect(allowed).toBe(maxRequests); + expect(blocked).toBe(burstSize - maxRequests); + expect(allowed + blocked).toBe(burstSize); + + const metrics = limiter.getMetrics(); + expect(metrics.totalRequests).toBe(burstSize); + expect(metrics.allowedRequests).toBe(maxRequests); + expect(metrics.blockedRequests).toBe(burstSize - maxRequests); + }); + + it('returns an identical, predictable 429 for every burst rejection', async () => { + const maxRequests = 3; + limiter = new RateLimiter(baseConfig({ maxRequests, windowMs: 60_000 })); + const req = makeRequest({ 'x-api-key': 'burst-envelope' }); + + const rejections: CapturedResponse[] = []; + for (let i = 0; i < maxRequests + 10; i++) { + const { res, captured } = makeResponse(); + const allowed = await limiter.handle(req, res); + if (!allowed) rejections.push(captured()); + } + + expect(rejections).toHaveLength(10); + for (const response of rejections) { + expect(response.status).toBe(429); + expect(response.headers['x-ratelimit-limit']).toBe(String(maxRequests)); + expect(response.headers['x-ratelimit-remaining']).toBe('0'); + expect(response.headers['x-ratelimit-reset']).toMatch(/^\d+$/); + expect(Number(response.headers['retry-after'])).toBeGreaterThanOrEqual(1); + expect(response.body).toMatchObject({ + success: false, + error: { code: 'RATE_LIMITED' }, + }); + expect(response.body.error.message).toContain('Rate limit exceeded'); + } + }); + + it('keeps the burst allowance honest under overlapping clients', async () => { + limiter = new RateLimiter(baseConfig({ maxRequests: 4, windowMs: 60_000 })); + + const clients = ['alpha', 'beta', 'gamma']; + const perClient = 6; + + const responses = await Promise.all( + clients.flatMap((client) => + Array.from({ length: perClient }, () => + limiter.handle(makeRequest({ 'x-api-key': client }), makeResponse().res), + ), + ), + ); + + // Each client independently gets exactly 4 of its 6 requests. + for (let i = 0; i < clients.length; i++) { + const slice = responses.slice(i * perClient, (i + 1) * perClient); + expect(slice.filter(Boolean)).toHaveLength(4); + } + expect(limiter.getMetrics().blockedRequests).toBe(clients.length * (perClient - 4)); + }); +}); + +describe('Rate limiting under repeated load across windows (#852)', () => { + beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date('2026-01-01T00:00:00.000Z')); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('re-admits a sustained client after the window rolls over', async () => { + const limiter = new RateLimiter(baseConfig({ maxRequests: 2, windowMs: 1_000 })); + const req = makeRequest({ 'x-api-key': 'sustained-client' }); + + try { + // Window 1: two allowed, the rest throttled. + expect(await limiter.handle(req, makeResponse().res)).toBe(true); + expect(await limiter.handle(req, makeResponse().res)).toBe(true); + expect(await limiter.handle(req, makeResponse().res)).toBe(false); + expect(limiter.getMetrics().blockedRequests).toBe(1); + + // Advance exactly one window: quota resets. + jest.advanceTimersByTime(1_000); + expect(await limiter.handle(req, makeResponse().res)).toBe(true); + expect(await limiter.handle(req, makeResponse().res)).toBe(true); + expect(await limiter.handle(req, makeResponse().res)).toBe(false); + + // Advance three windows at once: the client is fully re-admitted again. + jest.advanceTimersByTime(3_000); + expect(await limiter.handle(req, makeResponse().res)).toBe(true); + expect(await limiter.handle(req, makeResponse().res)).toBe(true); + + const metrics = limiter.getMetrics(); + // 5 allowed (2 + 2 + 1... actually 2+2+2) and 2 blocked across three windows. + expect(metrics.allowedRequests).toBe(6); + expect(metrics.blockedRequests).toBe(2); + } finally { + limiter.destroy(); + } + }); + + it('does not leak quota across windows for a client that only trickles requests', async () => { + const limiter = new RateLimiter(baseConfig({ maxRequests: 1, windowMs: 500 })); + const req = makeRequest({ 'x-api-key': 'trickle-client' }); + + try { + for (let window = 0; window < 4; window++) { + expect(await limiter.handle(req, makeResponse().res)).toBe(true); + // A second request in the same window is blocked. + expect(await limiter.handle(req, makeResponse().res)).toBe(false); + jest.advanceTimersByTime(500); + } + + const metrics = limiter.getMetrics(); + expect(metrics.allowedRequests).toBe(4); + expect(metrics.blockedRequests).toBe(4); + } finally { + limiter.destroy(); + } + }); +}); + +describe('Rate limiting client isolation and exemptions (#852)', () => { + let limiter: RateLimiter; + + afterEach(() => limiter?.destroy()); + + it('keeps API-key buckets and IP buckets independent', async () => { + limiter = new RateLimiter(baseConfig({ maxRequests: 1 })); + + const keyed = makeRequest({ 'x-api-key': 'keyed-client' }, '10.0.0.1'); + const anonymous = makeRequest({}, '10.0.0.2'); + + expect(await limiter.handle(keyed, makeResponse().res)).toBe(true); + expect(await limiter.handle(keyed, makeResponse().res)).toBe(false); + + // A different identity still gets its own allowance. + expect(await limiter.handle(anonymous, makeResponse().res)).toBe(true); + expect(await limiter.handle(anonymous, makeResponse().res)).toBe(false); + }); + + it('applies per-client overrides without affecting the default quota', async () => { + limiter = new RateLimiter( + baseConfig({ + maxRequests: 2, + clientOverrides: { + premium: { maxRequests: 5 }, + }, + }), + ); + + const premium = makeRequest({ 'x-api-key': 'premium' }); + for (let i = 0; i < 5; i++) { + expect(await limiter.handle(premium, makeResponse().res)).toBe(true); + } + expect(await limiter.handle(premium, makeResponse().res)).toBe(false); + + const standard = makeRequest({ 'x-api-key': 'standard' }); + expect(await limiter.handle(standard, makeResponse().res)).toBe(true); + expect(await limiter.handle(standard, makeResponse().res)).toBe(true); + expect(await limiter.handle(standard, makeResponse().res)).toBe(false); + }); + + it('treats a disabled limiter as a pass-through for all traffic', async () => { + limiter = new RateLimiter(baseConfig({ enabled: false, maxRequests: 1 })); + const req = makeRequest({ 'x-api-key': 'anything' }); + for (let i = 0; i < 50; i++) { + expect(await limiter.handle(req, makeResponse().res)).toBe(true); + } + }); +}); + +// --------------------------------------------------------------------------- +// End-to-end enforcement through a real HTTP server +// --------------------------------------------------------------------------- + +interface HttpResult { + status: number; + headers: http.IncomingHttpHeaders; + body: any; +} + +function httpGet( + port: number, + path: string, + headers: Record = {}, +): Promise { + return new Promise((resolve, reject) => { + const req = http.request({ host: '127.0.0.1', port, path, method: 'GET', headers }, (res) => { + let body = ''; + res.on('data', (chunk) => { + body += chunk; + }); + res.on('end', () => { + resolve({ + status: res.statusCode ?? 0, + headers: res.headers, + body: body ? JSON.parse(body) : undefined, + }); + }); + }); + req.on('error', reject); + req.end(); + }); +} + +describe('Rate limiting over HTTP (#852)', () => { + let server: http.Server | undefined; + + const startServer = async (rateLimit: RateLimitConfig): Promise => { + server = createEventsServer({ + port: 0, + stellarRpcUrl: 'https://soroban-testnet.stellar.org:443', + stellarNetworkPassphrase: 'Test SDF Network ; September 2015', + contractAddresses: [], + rateLimit, + }); + + await new Promise((resolve) => server!.listen(0, '127.0.0.1', () => resolve())); + const address = server!.address(); + if (!address || typeof address === 'string') throw new Error('Expected a TCP address'); + return address.port; + }; + + afterEach(async () => { + if (server) { + await new Promise((resolve) => server!.close(() => resolve())); + server = undefined; + } + }); + + it('enforces the quota end-to-end and returns the predictable 429 envelope', async () => { + const port = await startServer(baseConfig({ maxRequests: 2, windowMs: 60_000 })); + + const first = await httpGet(port, '/api/events', { 'x-api-key': 'e2e-client' }); + const second = await httpGet(port, '/api/events', { 'x-api-key': 'e2e-client' }); + const third = await httpGet(port, '/api/events', { 'x-api-key': 'e2e-client' }); + + expect(first.status).toBe(200); + expect(second.status).toBe(200); + expect(third.status).toBe(429); + expect(third.headers['x-ratelimit-limit']).toBe('2'); + expect(third.headers['x-ratelimit-remaining']).toBe('0'); + expect(Number(third.headers['retry-after'])).toBeGreaterThanOrEqual(1); + expect(third.body).toMatchObject({ success: false, error: { code: 'RATE_LIMITED' } }); + }); + + it('never blocks legitimate concurrent traffic from other clients', async () => { + const port = await startServer(baseConfig({ maxRequests: 3, windowMs: 60_000 })); + + const responses = await Promise.all([ + httpGet(port, '/api/events', { 'x-api-key': 'client-one' }), + httpGet(port, '/api/events', { 'x-api-key': 'client-two' }), + httpGet(port, '/api/events', { 'x-api-key': 'client-three' }), + httpGet(port, '/api/events', { 'x-api-key': 'client-one' }), + httpGet(port, '/api/events', { 'x-api-key': 'client-two' }), + ]); + + expect(responses.every((response) => response.status === 200)).toBe(true); + }); + + it('keeps /health and /api/rate-limit/metrics reachable while throttled', async () => { + const port = await startServer(baseConfig({ maxRequests: 1, windowMs: 60_000 })); + + const allowed = await httpGet(port, '/api/events', { 'x-api-key': 'exhausted' }); + const throttled = await httpGet(port, '/api/events', { 'x-api-key': 'exhausted' }); + expect(allowed.status).toBe(200); + expect(throttled.status).toBe(429); + + // Observability routes are exempt so callers can still diagnose throttling. + const metrics = await httpGet(port, '/api/rate-limit/metrics'); + expect(metrics.status).toBe(200); + expect(metrics.body.blockedRequests).toBeGreaterThanOrEqual(1); + + const health = await httpGet(port, '/health'); + // /health may report 200 or 503 depending on the (network-isolated) environment, + // but it must never be rate limited. + expect(health.status).not.toBe(429); + }); +}); diff --git a/listener/src/api/rate-limiter.test.ts b/listener/src/api/rate-limiter.test.ts index 9b3e6bdc..651e1975 100644 --- a/listener/src/api/rate-limiter.test.ts +++ b/listener/src/api/rate-limiter.test.ts @@ -1,4 +1,13 @@ -import { jest, describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from '@jest/globals'; +import { + jest, + describe, + it, + expect, + beforeEach, + afterEach, + beforeAll, + afterAll, +} from '@jest/globals'; import http from 'http'; import * as fs from 'fs'; import * as path from 'path'; @@ -31,7 +40,9 @@ const mockResponse = () => { let statusCode = 200; let body = ''; return { - setHeader: jest.fn().mockImplementation((name: any, val: any) => headers.set(name.toLowerCase(), String(val))), + setHeader: jest + .fn() + .mockImplementation((name: any, val: any) => headers.set(name.toLowerCase(), String(val))), writeHead: jest.fn().mockImplementation((code: any, h: any) => { statusCode = code; if (h) { @@ -102,7 +113,7 @@ describe('RateLimiter', () => { maxRequests: 5, clientOverrides: {}, }); - const req = mockRequest({ 'authorization': 'Bearer token-abc' }); + const req = mockRequest({ authorization: 'Bearer token-abc' }); const client = limiter.identifyClient(req); expect(client.clientId).toBe('token-abc'); @@ -189,8 +200,9 @@ describe('RateLimiter', () => { expect(res3._getHeaders().get('retry-after')).toBeDefined(); const body = JSON.parse(res3._getBody()); - expect(body.error).toBe('Too Many Requests'); - expect(body.message).toContain('Rate limit exceeded'); + expect(body.success).toBe(false); + expect(body.error.code).toBe('RATE_LIMITED'); + expect(body.error.message).toContain('Rate limit exceeded'); limiter.destroy(); }); @@ -249,7 +261,7 @@ describe('RateLimiter', () => { }); const req = mockRequest({}, '127.0.0.1'); - + // Initial metrics let metrics = limiter.getMetrics(); expect(metrics.totalRequests).toBe(0); @@ -381,7 +393,7 @@ describe('RateLimiter', () => { }); const req = mockRequest({ 'x-api-key': 'attacker-key' }, '8.8.8.8'); - + // Request 1: Allowed await limiter.handle(req, mockResponse()); @@ -396,12 +408,12 @@ describe('RateLimiter', () => { clientId: 'attacker...', clientType: 'API_KEY', endpoint: '/api/schedule', - }) + }), ); // Verify DB record // Need a small timeout to allow async DB insert to complete - await new Promise(resolve => setTimeout(resolve, 50)); + await new Promise((resolve) => setTimeout(resolve, 50)); const rows = await db.all('SELECT * FROM rate_limit_events'); expect(rows.length).toBe(1); @@ -426,7 +438,10 @@ describe('Events Server Rate Limiting Integration', () => { } }); - const makeRequest = (path: string, headers: Record = {}): Promise<{ status: number; headers: any }> => { + const makeRequest = ( + path: string, + headers: Record = {}, + ): Promise<{ status: number; headers: any }> => { return new Promise((resolve, reject) => { const req = http.request( { @@ -438,7 +453,7 @@ describe('Events Server Rate Limiting Integration', () => { }, (res) => { resolve({ status: res.statusCode!, headers: res.headers }); - } + }, ); req.on('error', reject); req.end(); @@ -497,29 +512,33 @@ describe('Events Server Rate Limiting Integration', () => { await makeRequest('/api/events'); // This one should be blocked // Fetch metrics - const metricsResponse = await new Promise<{ status: number; body: string }>((resolve, reject) => { - const req = http.request( - { - host: '127.0.0.1', - port, - path: '/api/rate-limit/metrics', - method: 'GET', - }, - (res) => { - let body = ''; - res.on('data', (chunk) => { body += chunk; }); - res.on('end', () => { - resolve({ status: res.statusCode!, body }); - }); - } - ); - req.on('error', reject); - req.end(); - }); + const metricsResponse = await new Promise<{ status: number; body: string }>( + (resolve, reject) => { + const req = http.request( + { + host: '127.0.0.1', + port, + path: '/api/rate-limit/metrics', + method: 'GET', + }, + (res) => { + let body = ''; + res.on('data', (chunk) => { + body += chunk; + }); + res.on('end', () => { + resolve({ status: res.statusCode!, body }); + }); + }, + ); + req.on('error', reject); + req.end(); + }, + ); expect(metricsResponse.status).toBe(200); const metrics = JSON.parse(metricsResponse.body); - + expect(metrics.totalRequests).toBeGreaterThanOrEqual(3); expect(metrics.allowedRequests).toBeGreaterThanOrEqual(2); expect(metrics.blockedRequests).toBeGreaterThanOrEqual(1); From 4be68ff2b0a63fed6db180d09c0a48d929b6fb79 Mon Sep 17 00:00:00 2001 From: oluwaseyi1996-netizen Date: Tue, 29 Sep 2026 20:26:43 +0000 Subject: [PATCH 3/4] feat(listener): add repeatable API load-testing workflow (#860) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces a load-testing workflow for the critical read endpoints, with documented scenarios and results that are comparable across changes. - load-test.config.json declares the scenarios (status, events, analytics, rate-limit-metrics) and the pass/fail thresholds; the format is documented in load-test.config.schema.md - src/utils/load-test-runner.ts is the dependency-free measurement core: throughput, nearest-rank p50/p90/p95/p99/min/mean/max latency, error rate (4xx vs 5xx split), threshold gating, and report-to-report comparison with a regression tolerance. Reports are schema-versioned JSON. - `npm run load-test` runs the documented scenarios in-process through Jest (boots createEventsServer on an ephemeral port and measures over real HTTP), which is the supported in-process path because the listener's runtime dependencies are mapped to test doubles by jest.config.js - `npm run load-test:external -- --url ` runs the same scenarios against an already-running listener, with --baseline/--fail-on-regression for gating - `npm run test:load` unit-tests the measurement core (metric maths, threshold gating, comparison) with an injected probe, so it is fast and socket-free - LOAD_TESTING.md documents the scenarios, how RPS/latency are measured, the baseline workflow, CI usage and how to read the results 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- listener/.gitignore | 5 + listener/LOAD_TESTING.md | 192 +++++++ listener/load-test.config.json | 59 ++ listener/load-test.config.schema.md | 73 +++ listener/package.json | 3 + .../src/__tests__/load-test-runner.test.ts | 252 +++++++++ .../src/__tests__/load-test.workflow.test.ts | 136 +++++ listener/src/scripts/load-test.ts | 180 +++++++ listener/src/utils/load-test-config.ts | 47 ++ listener/src/utils/load-test-http-probe.ts | 64 +++ listener/src/utils/load-test-reports.ts | 30 ++ listener/src/utils/load-test-runner.ts | 506 ++++++++++++++++++ 12 files changed, 1547 insertions(+) create mode 100644 listener/LOAD_TESTING.md create mode 100644 listener/load-test.config.json create mode 100644 listener/load-test.config.schema.md create mode 100644 listener/src/__tests__/load-test-runner.test.ts create mode 100644 listener/src/__tests__/load-test.workflow.test.ts create mode 100644 listener/src/scripts/load-test.ts create mode 100644 listener/src/utils/load-test-config.ts create mode 100644 listener/src/utils/load-test-http-probe.ts create mode 100644 listener/src/utils/load-test-reports.ts create mode 100644 listener/src/utils/load-test-runner.ts diff --git a/listener/.gitignore b/listener/.gitignore index caf428fe..36a4e844 100644 --- a/listener/.gitignore +++ b/listener/.gitignore @@ -3,3 +3,8 @@ dist/ .env *.log data/ + +# Generated load-test reports. These are environment-specific; keep a curated +# baseline (reports/load/baseline.json) committed so runs stay comparable. +reports/load/*.json +!reports/load/baseline.json diff --git a/listener/LOAD_TESTING.md b/listener/LOAD_TESTING.md new file mode 100644 index 00000000..2ce8af3d --- /dev/null +++ b/listener/LOAD_TESTING.md @@ -0,0 +1,192 @@ +# API Load Testing Workflow + +## Overview + +`npm run load-test` is a repeatable workflow for measuring the throughput and +latency of NotifyChain's critical API endpoints. It exists so a change can be +measured against the same scenarios, on the same machine, and compared to a +saved baseline instead of relying on anecdotes. + +The workflow covers the three things issue #860 asks for: + +| Requirement | How it is met | +| --- | --- | +| Test scenarios are documented | [`load-test.config.json`](./load-test.config.json) declares each scenario; [`load-test.config.schema.md`](./load-test.config.schema.md) documents the format | +| RPS and latency are measurable | Every run reports requests/second, p50/p90/p95/p99/max latency, and error rate per scenario and in aggregate | +| Results are comparable across changes | Reports are schema-versioned JSON written to `reports/load/`; runs can be diffed against a saved baseline, and gated in CI | + +It complements, rather than replaces, the existing suites: + +- `npm run test:load` — unit tests for the measurement core (metric maths, + threshold gating, baseline comparison). Fast and socket-free. +- `npm run test` — the functional suite, including the rate-limit behaviour + tests added in issue #852 (`src/api/rate-limit-scenarios.test.ts`). +- `npm run test:stress` / `npm run stress-test` — queue/event-processing stress + tests, which exercise the internal pipeline rather than HTTP endpoints. + +## Two ways to run + +| Command | Target | When to use | +| --- | --- | --- | +| `npm run load-test` | In-process `createEventsServer` on an ephemeral port | Local runs and CI, where there is no deployed listener | +| `npm run load-test:external -- --url ` | An already-running listener | Staging/production-like environments | + +Both drive the same scenarios, the same measurement core and the same report +format — only the target differs. + +> **Why the in-process run is a Jest spec.** The listener's third-party runtime +> dependencies (`@stellar/stellar-sdk`, `node-cache`, `uuid`) are not installed; +> `jest.config.js` maps them to test doubles. Jest is therefore the supported way +> to execute the API in-process, so `npm run load-test` drives +> [`src/__tests__/load-test.workflow.test.ts`](./src/__tests__/load-test.workflow.test.ts) +> with `LOAD_TEST=1`. The spec is skipped in the normal `npm test` run so the +> suite stays fast and side-effect free. + +## Quick start + +```bash +cd listener + +# 1. In-process run of the documented scenarios (CI-friendly, no server needed). +npm run load-test + +# 2. Against an already-running listener (e.g. `npm run dev`). +npm run load-test:external -- --url http://127.0.0.1:3000 + +# 3. Save a baseline, then compare later runs against it. +npm run load-test:external -- --url http://127.0.0.1:3000 --out reports/load/baseline.json +npm run load-test:external -- --url http://127.0.0.1:3000 --baseline reports/load/baseline.json + +# 4. Gate a change in CI (non-zero exit on regression). +npm run load-test:external -- --url http://127.0.0.1:3000 --baseline reports/load/baseline.json --fail-on-regression +``` + +No extra tooling (`k6`, `autocannon`, …) is required, so the workflow runs in the +same environment as the rest of the test suite. + +## Scenarios + +Scenarios are declared in [`load-test.config.json`](./load-test.config.json) and +cover the endpoints that are hot on the read path: + +| Scenario | Endpoint | Why it matters | +| --- | --- | --- | +| `status` | `GET /api/status` | Liveness/readiness probe; hit by every load balancer | +| `events` | `GET /api/events` | Event registry read; the hottest dashboard path | +| `analytics` | `GET /api/analytics` | Analytics snapshot read on every dashboard refresh | +| `rate-limit-metrics` | `GET /api/rate-limit/metrics` | Observability endpoint that must stay fast while clients are throttled | + +Add a scenario by appending an entry to the `scenarios` array — see the +[schema doc](./load-test.config.schema.md) for every field. Scenario `name`s are +the keys used to match results across reports, so keep them stable. + +## What is measured + +For each scenario, and across the whole run: + +- **Throughput (RPS)** — completed requests divided by the measured phase wall + time. +- **Latency** — nearest-rank percentiles (p50, p90, p95, p99) plus min/mean/max. + Sampled with `process.hrtime.bigint()`, which is monotonic and unaffected by + wall-clock changes. +- **Error rate** — non-2xx responses divided by total responses. Client (4xx) + and server (5xx) errors are counted separately, so a `429` from rate limiting + is not confused with a `500`. +- **Environment** — node version, platform, CPU count and total memory, so a + report is self-describing when compared later or on another machine. + +Each scenario runs a short warm-up phase (excluded from the numbers) before the +measured phase, and scenarios run **sequentially** so they don't contend for the +same event loop or server capacity. Both choices keep runs comparable. + +### Thresholds + +Thresholds live in the config and are evaluated on every run: + +```json +"thresholds": { + "maxErrorRate": 0.01, + "maxP95Ms": 250, + "minThroughputRps": 25 +} +``` + +A breached threshold is listed in the report's `failures` array and the CLI exits +with code `1` (the in-process workflow fails the assertion instead). + +## Baseline comparison + +A baseline is just a previously written report: + +```bash +npm run load-test:external -- --url http://127.0.0.1:3000 --out reports/load/baseline.json +``` + +Comparing later runs reports, per scenario, the absolute and relative change in +p95 latency, throughput and error rate. A scenario whose p95 grows by more than +the regression tolerance (default 10%) is flagged as a regression. With +`--fail-on-regression` the workflow exits non-zero so it can be used as a gate. + +For the in-process workflow, drop the report at +`reports/load/baseline.json`; the spec prints the comparison, and with +`LOAD_TEST_GATE=1` it asserts no regression. + +`reports/load/*.json` is git-ignored; commit `reports/load/baseline.json` +explicitly if you want the team to share one reference point. + +## CLI reference + +``` +npm run load-test:external -- [options] + + --config Scenario/threshold config (default: load-test.config.json) + --url Base URL of the listener to test (default: target.baseUrl) + --out Where to write the JSON report (default: reports/load/latest.json) + --baseline Compare against a previously saved report + --fail-on-regression Exit non-zero when the baseline comparison regresses + --concurrency Override concurrency for every scenario + --duration Override durationMs for every scenario + --quiet Only print the report + --help Show this help +``` + +**Exit codes** + +| Code | Meaning | +| --- | --- | +| `0` | Thresholds passed (and, when gating, no baseline regression) | +| `1` | A threshold was breached, or a regression was detected while `--fail-on-regression` is set | +| `2` | The workflow could not run (bad config, bad arguments) | + +## Using it in CI + +```bash +cd listener +npm ci +npm run load-test # in-process, no external dependencies +npm run load-test:external -- --url "$LISTENER_URL" --baseline reports/load/baseline.json --fail-on-regression +``` + +The in-process run starts the events server on an ephemeral port, so no external +services are needed. Rate limiting is **disabled** in the default config so the +run measures raw API capacity; enable it under `target.rateLimit` to load-test +the throttling path itself (see [RATE-LIMITING-GUIDE.md](./RATE-LIMITING-GUIDE.md)). + +Because absolute numbers depend on the machine, treat the baseline comparison as +the signal and absolute thresholds as a coarse sanity check. Run the baseline and +the candidate on the same runner for a meaningful diff. + +## Interpreting results + +- **Throughput stable, p95 flat** — no measurable regression; ship it. +- **p95 up > 10%, RPS down** — likely a hot-path change; the comparison output + names the scenario that regressed. +- **Error rate > threshold** — inspect the report's `clientErrors`/`serverErrors` + split before assuming a regression. + +## Follow-ups + +- Publish a shared, machine-independent baseline (e.g. from a dedicated CI + runner) if the team wants absolute gating rather than relative comparison. +- Extend the scenario set to mutating endpoints (`POST /api/webhooks`) with + authenticated fixtures, which needs request signing (#491) to be wired in. diff --git a/listener/load-test.config.json b/listener/load-test.config.json new file mode 100644 index 00000000..6af7cfd0 --- /dev/null +++ b/listener/load-test.config.json @@ -0,0 +1,59 @@ +{ + "$schema": "./load-test.config.schema.md", + "target": { + "baseUrl": "http://127.0.0.1:3000", + "rateLimit": { + "enabled": false, + "windowMs": 60000, + "maxRequests": 1000000, + "clientOverrides": {} + } + }, + "scenarios": [ + { + "name": "status", + "description": "Service status - the liveness/readiness probe used by load balancers.", + "method": "GET", + "path": "/api/status", + "concurrency": 10, + "durationMs": 3000, + "warmupMs": 500, + "maxRequests": 3000 + }, + { + "name": "events", + "description": "Event registry read - the hottest read path for the dashboard.", + "method": "GET", + "path": "/api/events", + "concurrency": 10, + "durationMs": 3000, + "warmupMs": 500, + "maxRequests": 3000 + }, + { + "name": "analytics", + "description": "Analytics snapshot - read by the metrics dashboard on every refresh.", + "method": "GET", + "path": "/api/analytics", + "concurrency": 10, + "durationMs": 3000, + "warmupMs": 500, + "maxRequests": 3000 + }, + { + "name": "rate-limit-metrics", + "description": "Rate-limit observability endpoint - must stay fast while clients are throttled.", + "method": "GET", + "path": "/api/rate-limit/metrics", + "concurrency": 5, + "durationMs": 2000, + "warmupMs": 300, + "maxRequests": 1500 + } + ], + "thresholds": { + "maxErrorRate": 0.01, + "maxP95Ms": 250, + "minThroughputRps": 25 + } +} diff --git a/listener/load-test.config.schema.md b/listener/load-test.config.schema.md new file mode 100644 index 00000000..d9a642ee --- /dev/null +++ b/listener/load-test.config.schema.md @@ -0,0 +1,73 @@ +# `load-test.config.json` schema + +Human-readable reference for the config consumed by +[`src/scripts/load-test.ts`](./src/scripts/load-test.ts) (issue #860). Types live +in [`src/utils/load-test-runner.ts`](./src/utils/load-test-runner.ts) +(`LoadTestConfig`, `LoadTestScenario`, `LoadTestThresholds`). + +## Top level + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `$schema` | string | no | Point at this document for editor hints | +| `target` | object | no | Defaults for the target server | +| `scenarios` | object[] | **yes** | One or more scenarios to execute | +| `thresholds` | object | **yes** | Pass/fail gates evaluated on every run | + +## `target` + +| Field | Type | Default | Description | +| --- | --- | --- | --- | +| `baseUrl` | string | `http://127.0.0.1:3000` | Default base URL for the external CLI when `--url` is omitted | +| `rateLimit` | object | disabled | `RateLimitConfig` applied to the in-process server by the Jest workflow (`enabled`, `windowMs`, `maxRequests`, `clientOverrides`) | + +Rate limiting is disabled by default so the workflow measures raw API capacity. +Enable it to load-test the throttling path. + +## `scenarios[]` + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | string | **yes** | Stable identifier; the key used to match scenarios across reports | +| `description` | string | no | Human-readable note on which critical path the scenario covers | +| `method` | `GET` \| `POST` \| `PUT` \| `DELETE` | **yes** | HTTP method | +| `path` | string | **yes** | Request path, e.g. `/api/status` | +| `headers` | object | no | Extra request headers | +| `body` | string | no | Already-serialized request body | +| `concurrency` | number | **yes** | In-flight requests during the phase | +| `durationMs` | number | **yes** | Length of the measured phase, in milliseconds | +| `warmupMs` | number | no | Warm-up phase, executed but excluded from the report | +| `maxRequests` | number | no | Hard cap on measured requests (AND-ed with `durationMs`); useful for bounded/CI runs | + +## `thresholds` + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `maxErrorRate` | number | **yes** | Maximum tolerated fraction of non-2xx responses (`0.01` = 1%) | +| `maxP95Ms` | number | **yes** | Maximum tolerated p95 latency per scenario, in ms | +| `minThroughputRps` | number | no | Minimum aggregate throughput, in requests/second | + +## Example + +```json +{ + "$schema": "./load-test.config.schema.md", + "target": { + "baseUrl": "http://127.0.0.1:3000", + "rateLimit": { "enabled": false, "windowMs": 60000, "maxRequests": 1000000, "clientOverrides": {} } + }, + "scenarios": [ + { + "name": "status", + "description": "Liveness/readiness probe used by load balancers.", + "method": "GET", + "path": "/api/status", + "concurrency": 10, + "durationMs": 3000, + "warmupMs": 500, + "maxRequests": 3000 + } + ], + "thresholds": { "maxErrorRate": 0.01, "maxP95Ms": 250, "minThroughputRps": 25 } +} +``` diff --git a/listener/package.json b/listener/package.json index 4a5508e9..7af5cb9b 100644 --- a/listener/package.json +++ b/listener/package.json @@ -11,6 +11,9 @@ "format:check": "node ./node_modules/prettier/bin/prettier.cjs --check \"src/**/*.ts\" --config ../.prettierrc", "test": "node ./node_modules/jest/bin/jest.js", "test:stress": "node ./node_modules/jest/bin/jest.js src/__tests__/stress.test.ts --runInBand --detectOpenHandles", + "test:load": "node ./node_modules/jest/bin/jest.js src/__tests__/load-test-runner.test.ts --runInBand", + "load-test": "LOAD_TEST=1 node ./node_modules/jest/bin/jest.js src/__tests__/load-test.workflow.test.ts --runInBand", + "load-test:external": "ts-node --transpile-only src/scripts/load-test.ts", "stress-test": "ts-node src/scripts/run-stress-tests.ts", "migrate": "ts-node src/scripts/migrate-db.ts", "migrate:templates": "ts-node src/scripts/migrate-templates.ts", diff --git a/listener/src/__tests__/load-test-runner.test.ts b/listener/src/__tests__/load-test-runner.test.ts new file mode 100644 index 00000000..023e5620 --- /dev/null +++ b/listener/src/__tests__/load-test-runner.test.ts @@ -0,0 +1,252 @@ +/** + * Load-testing workflow tests (issue #860) + * + * These exercise the measurement core directly with an injected probe so the + * behaviour is deterministic and socket-free: percentile maths, request/error + * accounting, threshold gating and baseline comparison. + */ + +import { describe, it, expect, jest } from '@jest/globals'; + +import { + LOAD_REPORT_SCHEMA_VERSION, + compareLoadReports, + formatComparison, + formatLoadReport, + percentile, + runLoadTest, + runScenario, + summarizeLatencies, + type LoadReport, + type LoadTestProbe, + type LoadTestScenario, +} from '../utils/load-test-runner'; + +/** Probe that always succeeds and reports a fixed latency. */ +function constantProbe(status = 200, durationMs = 5): LoadTestProbe { + return jest.fn(async () => ({ status, durationMs })); +} + +function scenario(overrides: Partial = {}): LoadTestScenario { + return { + name: 'status', + method: 'GET', + path: '/api/status', + concurrency: 2, + durationMs: 5_000, + maxRequests: 8, + ...overrides, + }; +} + +describe('percentile (#860)', () => { + it('returns 0 for an empty sample', () => { + expect(percentile([], 95)).toBe(0); + }); + + it('uses nearest-rank for typical percentiles', () => { + const sorted = Array.from({ length: 100 }, (_, i) => i + 1); + expect(percentile(sorted, 50)).toBe(50); + expect(percentile(sorted, 90)).toBe(90); + expect(percentile(sorted, 95)).toBe(95); + expect(percentile(sorted, 99)).toBe(99); + }); + + it('clamps the boundaries to the sample', () => { + const sorted = [3, 9, 12]; + expect(percentile(sorted, 0)).toBe(3); + expect(percentile(sorted, 100)).toBe(12); + }); +}); + +describe('summarizeLatencies (#860)', () => { + it('reports min/mean/percentiles/max from unsorted input', () => { + const stats = summarizeLatencies([40, 10, 30, 20]); + expect(stats.min).toBe(10); + expect(stats.max).toBe(40); + expect(stats.mean).toBe(25); + expect(stats.p50).toBe(20); + expect(stats.p99).toBe(40); + }); + + it('returns zeroed stats for no samples', () => { + expect(summarizeLatencies([])).toEqual({ + min: 0, + mean: 0, + p50: 0, + p90: 0, + p95: 0, + p99: 0, + max: 0, + }); + }); +}); + +describe('runScenario (#860)', () => { + it('measures exactly maxRequests and counts successes', async () => { + const probe = constantProbe(200, 4); + const result = await runScenario(scenario({ concurrency: 4, maxRequests: 20 }), probe); + + expect(result.requests).toBe(20); + expect(result.success).toBe(20); + expect(result.errorRate).toBe(0); + expect(result.throughputRps).toBeGreaterThan(0); + expect(result.latencyMs.p50).toBe(4); + expect(probe).toHaveBeenCalledTimes(20); + }); + + it('separates client (4xx) and server (5xx) errors', async () => { + let call = 0; + const probe: LoadTestProbe = jest.fn(async () => { + call += 1; + const status = call % 4 === 0 ? 500 : call % 2 === 0 ? 429 : 200; + return { status, durationMs: 1 }; + }); + + const result = await runScenario(scenario({ concurrency: 1, maxRequests: 8 }), probe); + + expect(result.requests).toBe(8); + expect(result.success + result.clientErrors + result.serverErrors).toBe(8); + expect(result.serverErrors).toBeGreaterThan(0); + expect(result.clientErrors).toBeGreaterThan(0); + expect(result.errorRate).toBeGreaterThan(0); + }); + + it('does not count warm-up requests in the report', async () => { + const probe = constantProbe(200, 1); + const result = await runScenario( + scenario({ concurrency: 2, durationMs: 20, warmupMs: 20, maxRequests: undefined }), + probe, + ); + + // The report only reflects the measured phase, so it can't distinguish + // warm-up calls; but the probe must have been called at least once. + expect(result.requests).toBeGreaterThan(0); + expect(probe).toHaveBeenCalled(); + }); +}); + +describe('runLoadTest threshold gating (#860)', () => { + const config = { + scenarios: [scenario({ name: 'a', maxRequests: 10 }), scenario({ name: 'b', maxRequests: 10 })], + thresholds: { maxErrorRate: 0.05, maxP95Ms: 100 }, + }; + + it('passes when every threshold is satisfied and produces a schemaVersioned report', async () => { + const report = await runLoadTest(config, constantProbe(200, 10)); + expect(report.schemaVersion).toBe(LOAD_REPORT_SCHEMA_VERSION); + expect(report.passed).toBe(true); + expect(report.failures).toEqual([]); + expect(report.totals.requests).toBe(20); + expect(report.scenarios.map((s) => s.name)).toEqual(['a', 'b']); + }); + + it('fails when the aggregate error rate breaches the threshold', async () => { + const report = await runLoadTest(config, constantProbe(500, 10)); + expect(report.passed).toBe(false); + expect(report.totals.errorRate).toBe(1); + expect(report.failures.join(' ')).toContain('error rate'); + }); + + it('fails when a scenario p95 breaches the latency threshold', async () => { + const report = await runLoadTest(config, constantProbe(200, 500)); + expect(report.passed).toBe(false); + expect(report.failures.join(' ')).toContain('p95'); + }); + + it('fails when throughput falls below the minimum', async () => { + const report = await runLoadTest( + { ...config, thresholds: { ...config.thresholds, minThroughputRps: 10_000_000 } }, + constantProbe(200, 1), + ); + expect(report.passed).toBe(false); + expect(report.failures.join(' ')).toContain('throughput'); + }); +}); + +describe('compareLoadReports (#860)', () => { + function reportWith(name: string, p95: number, rps: number, errorRate = 0): LoadReport { + const scenarioResult = { + name, + method: 'GET' as const, + path: '/api/status', + concurrency: 1, + durationMs: 1000, + requests: 100, + success: 100, + clientErrors: 0, + serverErrors: 0, + errorRate, + throughputRps: rps, + latencyMs: { min: p95, mean: p95, p50: p95, p90: p95, p95, p99: p95, max: p95 }, + }; + return { + schemaVersion: LOAD_REPORT_SCHEMA_VERSION, + generatedAt: new Date(0).toISOString(), + environment: { node: 'v0', platform: 'test', release: 'test', cpus: 1, totalMemoryMb: 1 }, + scenarios: [scenarioResult], + totals: { requests: 100, errorRate, throughputRps: rps, latencyMs: scenarioResult.latencyMs }, + thresholds: { maxErrorRate: 0.1, maxP95Ms: 1000 }, + passed: true, + failures: [], + }; + } + + it('detects a latency regression beyond the tolerance', () => { + const comparison = compareLoadReports( + reportWith('status', 100, 1000), + reportWith('status', 150, 900), + ); + const scenarioComparison = comparison.scenarios[0]; + expect(scenarioComparison.status).toBe('compared'); + expect(scenarioComparison.p95Ms?.delta).toBe(50); + expect(scenarioComparison.p95Ms?.deltaPct).toBeCloseTo(0.5); + expect(comparison.passed).toBe(false); + expect(comparison.regressions.length).toBe(1); + }); + + it('treats an improvement as a pass', () => { + const comparison = compareLoadReports( + reportWith('status', 100, 1000), + reportWith('status', 80, 1200), + ); + expect(comparison.passed).toBe(true); + expect(comparison.regressions).toEqual([]); + }); + + it('honours a wider regression tolerance', () => { + const comparison = compareLoadReports( + reportWith('status', 100, 1000), + reportWith('status', 105, 1000), + 0.1, + ); + expect(comparison.passed).toBe(true); + }); + + it('reports added and removed scenarios', () => { + const comparison = compareLoadReports( + reportWith('status', 100, 1000), + reportWith('events', 100, 1000), + ); + const statuses = comparison.scenarios.map((s) => s.status).sort(); + expect(statuses).toEqual(['added', 'removed']); + }); +}); + +describe('report formatting (#860)', () => { + it('renders a readable report and comparison', async () => { + const report = await runLoadTest( + { + scenarios: [scenario({ name: 'status', maxRequests: 4 })], + thresholds: { maxErrorRate: 0.1, maxP95Ms: 1000 }, + }, + constantProbe(200, 3), + ); + const text = formatLoadReport(report); + expect(text).toContain('status'); + expect(text).toContain('Thresholds: PASS'); + + const comparison = compareLoadReports(report, report); + expect(formatComparison(comparison)).toContain('Comparison vs baseline'); + }); +}); diff --git a/listener/src/__tests__/load-test.workflow.test.ts b/listener/src/__tests__/load-test.workflow.test.ts new file mode 100644 index 00000000..fdb2d58f --- /dev/null +++ b/listener/src/__tests__/load-test.workflow.test.ts @@ -0,0 +1,136 @@ +/** + * In-process API load-testing workflow (issue #860) + * + * This is the `npm run load-test` entry point: it boots the real + * `createEventsServer` on an ephemeral port and runs the scenarios documented + * in `load-test.config.json` through a real HTTP probe, then writes a + * schema-versioned report to `reports/load/latest.json` that can be compared + * against a committed baseline. + * + * It lives under Jest deliberately. The listener's third-party runtime + * dependencies (`@stellar/stellar-sdk`, `node-cache`, `uuid`) are not installed + * — they are mapped to test doubles by `jest.config.js` — so Jest is the + * supported way to execute the API in-process. The external CLI + * (`npm run load-test:external`) covers the "already-running server" case. + * + * Gated behind `LOAD_TEST=1` so the regular `npm test` suite stays fast and + * side-effect free. + */ + +import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; +import fs from 'fs'; +import http from 'http'; + +import { createEventsServer } from '../api/events-server'; +import type { RateLimitConfig } from '../types'; +import { loadLoadTestConfig } from '../utils/load-test-config'; +import { makeHttpProbe } from '../utils/load-test-http-probe'; +import { + DEFAULT_BASELINE_PATH, + DEFAULT_REPORT_PATH, + readLoadReport, + writeLoadReport, +} from '../utils/load-test-reports'; +import { + LOAD_REPORT_SCHEMA_VERSION, + compareLoadReports, + formatComparison, + formatLoadReport, + runLoadTest, + type LoadReport, +} from '../utils/load-test-runner'; + +jest.mock('../utils/logger', () => ({ + __esModule: true, + default: { + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + }, +})); + +jest.setTimeout(120_000); + +/** Rate limiting is off for a capacity run; the config can override this. */ +const DISABLED_RATE_LIMIT: RateLimitConfig = { + enabled: false, + windowMs: 60_000, + maxRequests: 1_000_000, + clientOverrides: {}, +}; + +const describeWorkflow = process.env.LOAD_TEST === '1' ? describe : describe.skip; + +describeWorkflow('API load-testing workflow (#860)', () => { + let server: http.Server | undefined; + let baseUrl = ''; + let report: LoadReport; + + beforeAll(async () => { + const config = loadLoadTestConfig(); + + server = createEventsServer({ + port: 0, + stellarRpcUrl: process.env.STELLAR_RPC_URL ?? 'https://soroban-testnet.stellar.org', + stellarNetworkPassphrase: 'Test SDF Network ; September 2015', + contractAddresses: [], + rateLimit: config.target?.rateLimit ?? DISABLED_RATE_LIMIT, + }); + + await new Promise((resolve) => server!.listen(0, '127.0.0.1', () => resolve())); + const address = server.address(); + if (!address || typeof address === 'string') throw new Error('Expected a TCP address'); + baseUrl = `http://127.0.0.1:${address.port}`; + + report = await runLoadTest(config, makeHttpProbe(baseUrl)); + writeLoadReport(DEFAULT_REPORT_PATH, report); + process.stdout.write(`\n${formatLoadReport(report)}\n`); + }); + + afterAll(async () => { + if (server) { + await new Promise((resolve) => server!.close(() => resolve())); + server = undefined; + } + }); + + it('executes every documented scenario and stays within thresholds', () => { + const { scenarios } = loadLoadTestConfig(); + + expect(report.schemaVersion).toBe(LOAD_REPORT_SCHEMA_VERSION); + expect(report.scenarios.map((scenario) => scenario.name)).toEqual( + scenarios.map((scenario) => scenario.name), + ); + expect(report.failures).toEqual([]); + expect(report.passed).toBe(true); + }); + + it('measures throughput and latency for the critical endpoints', () => { + for (const scenario of report.scenarios) { + expect(scenario.requests).toBeGreaterThan(0); + expect(scenario.throughputRps).toBeGreaterThan(0); + expect(scenario.latencyMs.p95).toBeGreaterThanOrEqual(0); + // Rate limiting is disabled, so nothing should be throttled. + expect(scenario.clientErrors).toBe(0); + expect(scenario.serverErrors).toBe(0); + } + expect(report.totals.requests).toBeGreaterThan(0); + expect(report.totals.throughputRps).toBeGreaterThan(0); + }); + + it('can be compared against a committed baseline', () => { + if (!fs.existsSync(DEFAULT_BASELINE_PATH)) { + // No baseline committed yet: write this run as one to start comparing. + process.stdout.write(`\nNo baseline at ${DEFAULT_BASELINE_PATH}; skipping comparison.\n`); + return; + } + + const baseline = readLoadReport(DEFAULT_BASELINE_PATH); + const comparison = compareLoadReports(baseline, report); + process.stdout.write(`\n${formatComparison(comparison)}\n`); + + if (process.env.LOAD_TEST_GATE === '1') { + expect(comparison.passed).toBe(true); + } + }); +}); diff --git a/listener/src/scripts/load-test.ts b/listener/src/scripts/load-test.ts new file mode 100644 index 00000000..be4fba34 --- /dev/null +++ b/listener/src/scripts/load-test.ts @@ -0,0 +1,180 @@ +#!/usr/bin/env ts-node + +/** + * Repeatable load-testing workflow (issue #860) — external target + * + * Runs the scenarios documented in `load-test.config.json` against a running + * NotifyChain listener and writes a JSON report that can be compared across + * changes. + * + * This entry point talks to an already-running server over HTTP, which is how + * you load-test a deployed listener. To run the same scenarios in-process + * (no external server, no network) use `npm run load-test`, which drives the + * scenarios through the Jest workflow instead — the listener's runtime + * dependency mapping (`@stellar/stellar-sdk`, `node-cache`, `uuid`) is only + * wired up under Jest, so that is the supported in-process path. + * + * Usage: + * npm run load-test:external -- --url http://127.0.0.1:3000 + * npm run load-test:external -- --url https://listener.example.com --out reports/load/staging.json + * npm run load-test:external -- --url http://127.0.0.1:3000 --baseline reports/load/baseline.json + * npm run load-test:external -- --url http://127.0.0.1:3000 --baseline reports/load/baseline.json --fail-on-regression + * + * Exit codes: + * 0 thresholds (and, when gating, baseline comparison) passed + * 1 a threshold was breached, or a regression was detected while gating + * 2 the workflow could not run (bad config, bad arguments) + */ + +import path from 'path'; + +import { + DEFAULT_CONFIG_PATH, + loadLoadTestConfig, + type LoadTestFileConfig, +} from '../utils/load-test-config'; +import { makeHttpProbe } from '../utils/load-test-http-probe'; +import { DEFAULT_REPORT_PATH, readLoadReport, writeLoadReport } from '../utils/load-test-reports'; +import { + compareLoadReports, + formatComparison, + formatLoadReport, + runLoadTest, + type LoadTestConfig, +} from '../utils/load-test-runner'; + +interface CliOptions { + configPath: string; + url?: string; + outPath: string; + baselinePath?: string; + failOnRegression: boolean; + concurrency?: number; + durationMs?: number; + quiet: boolean; +} + +function parseArgs(argv: string[]): CliOptions { + const options: CliOptions = { + configPath: DEFAULT_CONFIG_PATH, + outPath: DEFAULT_REPORT_PATH, + failOnRegression: false, + quiet: false, + }; + + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]; + const next = (): string => { + const value = argv[++i]; + if (value === undefined) throw new Error(`Missing value for ${arg}`); + return value; + }; + + switch (arg) { + case '--config': + options.configPath = path.resolve(next()); + break; + case '--url': + options.url = next(); + break; + case '--out': + options.outPath = path.resolve(next()); + break; + case '--baseline': + options.baselinePath = path.resolve(next()); + break; + case '--concurrency': + options.concurrency = Number(next()); + break; + case '--duration': + options.durationMs = Number(next()); + break; + case '--fail-on-regression': + options.failOnRegression = true; + break; + case '--quiet': + options.quiet = true; + break; + case '--help': + case '-h': + printHelp(); + process.exit(0); + break; + default: + throw new Error(`Unknown argument: ${arg}`); + } + } + + return options; +} + +function printHelp(): void { + process.stdout.write( + [ + 'NotifyChain load-testing workflow (external target)', + '', + 'Options:', + ' --config Scenario/threshold config (default: load-test.config.json)', + ' --url Base URL of the listener to test (default: target.baseUrl in the config)', + ' --out Where to write the JSON report (default: reports/load/latest.json)', + ' --baseline Compare against a previously saved report', + ' --fail-on-regression Exit non-zero when the baseline comparison regresses', + ' --concurrency Override concurrency for every scenario', + ' --duration Override durationMs for every scenario', + ' --quiet Only print the report', + '', + ].join('\n'), + ); +} + +function applyOverrides(config: LoadTestFileConfig, options: CliOptions): LoadTestConfig { + const scenarios = config.scenarios.map((scenario) => ({ + ...scenario, + concurrency: options.concurrency ?? scenario.concurrency, + durationMs: options.durationMs ?? scenario.durationMs, + })); + return { scenarios, thresholds: config.thresholds }; +} + +async function main(): Promise { + const options = parseArgs(process.argv.slice(2)); + const config = loadLoadTestConfig(options.configPath); + const runConfig = applyOverrides(config, options); + + const baseUrl = options.url ?? config.target?.baseUrl; + if (!baseUrl) { + throw new Error('No target: pass --url or set target.baseUrl in the config'); + } + + if (!options.quiet) { + process.stdout.write( + `Load testing ${baseUrl} across ${runConfig.scenarios.length} scenario(s)...\n\n`, + ); + } + + const report = await runLoadTest(runConfig, makeHttpProbe(baseUrl)); + writeLoadReport(options.outPath, report); + process.stdout.write(`${formatLoadReport(report)}\n`); + process.stdout.write(`\nReport written to ${options.outPath}\n`); + + let regressionFailed = false; + if (options.baselinePath) { + const baseline = readLoadReport(options.baselinePath); + const comparison = compareLoadReports(baseline, report); + process.stdout.write(`\n${formatComparison(comparison)}\n`); + if (!comparison.passed && options.failOnRegression) { + regressionFailed = true; + process.stdout.write('\nBaseline regressions detected while --fail-on-regression is set.\n'); + } + } + + if (!report.passed || regressionFailed) { + process.exitCode = 1; + } +} + +main().catch((error: unknown) => { + const message = error instanceof Error ? error.message : String(error); + process.stderr.write(`Load-test workflow failed: ${message}\n`); + process.exit(2); +}); diff --git a/listener/src/utils/load-test-config.ts b/listener/src/utils/load-test-config.ts new file mode 100644 index 00000000..19d94fd1 --- /dev/null +++ b/listener/src/utils/load-test-config.ts @@ -0,0 +1,47 @@ +/** + * Load-test configuration loading (issue #860) + * + * Shared by the external CLI (`src/scripts/load-test.ts`) and the in-process + * Jest workflow (`src/__tests__/load-test.workflow.test.ts`) so both interpret + * `load-test.config.json` identically. + */ + +import fs from 'fs'; +import path from 'path'; + +import type { RateLimitConfig } from '../types'; +import type { LoadTestConfig } from './load-test-runner'; + +export interface LoadTestTarget { + /** Default base URL used by the external CLI when `--url` is omitted. */ + baseUrl?: string; + /** + * Rate-limit config applied to the in-process server by the Jest workflow. + * Rate limiting is disabled by default so the workflow measures raw API + * capacity; enable it to load-test the throttling path itself. + */ + rateLimit?: RateLimitConfig; +} + +export interface LoadTestFileConfig extends LoadTestConfig { + $schema?: string; + target?: LoadTestTarget; +} + +export const DEFAULT_CONFIG_PATH = path.resolve(__dirname, '..', '..', 'load-test.config.json'); + +/** Reads and minimally validates the config file. Throws on unusable input. */ +export function loadLoadTestConfig(configPath: string = DEFAULT_CONFIG_PATH): LoadTestFileConfig { + if (!fs.existsSync(configPath)) { + throw new Error(`Config not found: ${configPath}`); + } + + const parsed = JSON.parse(fs.readFileSync(configPath, 'utf8')) as LoadTestFileConfig; + if (!Array.isArray(parsed.scenarios) || parsed.scenarios.length === 0) { + throw new Error('Config must define at least one scenario'); + } + if (!parsed.thresholds) { + throw new Error('Config must define thresholds'); + } + return parsed; +} diff --git a/listener/src/utils/load-test-http-probe.ts b/listener/src/utils/load-test-http-probe.ts new file mode 100644 index 00000000..f5442a5e --- /dev/null +++ b/listener/src/utils/load-test-http-probe.ts @@ -0,0 +1,64 @@ +/** + * HTTP probe for the load-testing workflow (issue #860) + * + * Kept separate from the measurement core so `load-test-runner.ts` stays + * socket-free (and therefore deterministic under unit test). Latency is sampled + * with `process.hrtime.bigint()` because it is monotonic and unaffected by + * wall-clock adjustments. + */ + +import http from 'http'; +import https from 'https'; + +import type { LoadTestProbe, LoadTestScenario } from './load-test-runner'; + +/** + * Builds a probe that issues real HTTP requests against `baseUrl`. + * + * Network failures resolve with status `0` rather than rejecting, so a flaky + * connection shows up in the error rate instead of aborting the whole run. + */ +export function makeHttpProbe(baseUrl: string): LoadTestProbe { + const target = new URL(baseUrl); + const secure = target.protocol === 'https:'; + const port = target.port ? Number(target.port) : secure ? 443 : 80; + const agent = secure + ? new https.Agent({ keepAlive: true, maxSockets: 256 }) + : new http.Agent({ keepAlive: true, maxSockets: 256 }); + + return (scenario: LoadTestScenario) => + new Promise((resolve) => { + const startedAt = process.hrtime.bigint(); + const request = (secure ? https : http).request( + { + host: target.hostname, + port, + path: scenario.path, + method: scenario.method, + agent, + headers: { + 'content-type': 'application/json', + ...(scenario.headers ?? {}), + }, + }, + (response) => { + response.on('data', () => { + /* drain the body so the socket can be reused */ + }); + response.on('end', () => { + resolve({ + status: response.statusCode ?? 0, + durationMs: Number(process.hrtime.bigint() - startedAt) / 1e6, + }); + }); + }, + ); + + request.on('error', () => { + resolve({ status: 0, durationMs: Number(process.hrtime.bigint() - startedAt) / 1e6 }); + }); + + if (scenario.body) request.write(scenario.body); + request.end(); + }); +} diff --git a/listener/src/utils/load-test-reports.ts b/listener/src/utils/load-test-reports.ts new file mode 100644 index 00000000..7b2e1cbe --- /dev/null +++ b/listener/src/utils/load-test-reports.ts @@ -0,0 +1,30 @@ +/** + * Report persistence for the load-testing workflow (issue #860) + * + * Reports are schema-versioned JSON (`LOAD_REPORT_SCHEMA_VERSION`), which is + * what makes "results comparable across changes" possible: a report can be + * committed as a baseline and diffed by `compareLoadReports`. + */ + +import fs from 'fs'; +import path from 'path'; + +import type { LoadReport } from './load-test-runner'; + +export const DEFAULT_REPORT_DIR = path.resolve(__dirname, '..', '..', 'reports', 'load'); +export const DEFAULT_REPORT_PATH = path.join(DEFAULT_REPORT_DIR, 'latest.json'); +export const DEFAULT_BASELINE_PATH = path.join(DEFAULT_REPORT_DIR, 'baseline.json'); + +/** Writes a report as pretty-printed JSON, creating the directory if needed. */ +export function writeLoadReport(outPath: string, report: LoadReport): void { + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + fs.writeFileSync(outPath, `${JSON.stringify(report, null, 2)}\n`); +} + +/** Reads a previously written report. Throws if it is missing. */ +export function readLoadReport(reportPath: string): LoadReport { + if (!fs.existsSync(reportPath)) { + throw new Error(`Report not found: ${reportPath}`); + } + return JSON.parse(fs.readFileSync(reportPath, 'utf8')) as LoadReport; +} diff --git a/listener/src/utils/load-test-runner.ts b/listener/src/utils/load-test-runner.ts new file mode 100644 index 00000000..bd04ab47 --- /dev/null +++ b/listener/src/utils/load-test-runner.ts @@ -0,0 +1,506 @@ +/** + * Load-testing runner (issue #860) + * + * A small, dependency-free measurement core for the NotifyChain API. It is + * deliberately separated from the CLI (`src/scripts/load-test.ts`) so the + * metric maths, threshold gating and run-to-run comparison logic can be unit + * tested without opening a socket. + * + * The workflow the issue asks for falls out of three exported pieces: + * 1. `runLoadTest` – executes documented scenarios and returns a report + * 2. `compareLoadReports` – diffs a report against a saved baseline + * 3. `formatLoadReport` / `formatComparison` – human-readable summaries + * + * Reports are plain JSON with a `schemaVersion`, so results can be committed + * as a baseline and compared across changes. + */ + +import os from 'os'; + +export const LOAD_REPORT_SCHEMA_VERSION = 1; + +export type LoadTestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'; + +export interface LoadTestScenario { + /** Stable identifier used to match scenarios across reports. */ + name: string; + /** Human-readable note about which critical path the scenario covers. */ + description?: string; + method: LoadTestMethod; + path: string; + headers?: Record; + /** Raw request body sent with the scenario (already serialized). */ + body?: string; + /** Number of concurrent in-flight requests. */ + concurrency: number; + /** Length of the measured phase, in milliseconds. */ + durationMs: number; + /** Optional warm-up phase that is executed but excluded from the report. */ + warmupMs?: number; + /** + * Optional hard cap on measured requests. Combined with `durationMs` via a + * logical AND, this keeps CI runs bounded and makes small runs deterministic. + */ + maxRequests?: number; +} + +export interface LoadTestThresholds { + /** Maximum tolerated fraction of non-2xx responses (0..1). */ + maxErrorRate: number; + /** Maximum tolerated p95 latency, in milliseconds. */ + maxP95Ms: number; + /** Optional minimum aggregate throughput, in requests per second. */ + minThroughputRps?: number; +} + +export interface LoadTestConfig { + scenarios: LoadTestScenario[]; + thresholds: LoadTestThresholds; +} + +export interface LatencyStats { + min: number; + mean: number; + p50: number; + p90: number; + p95: number; + p99: number; + max: number; +} + +export interface ScenarioResult { + name: string; + method: LoadTestMethod; + path: string; + concurrency: number; + durationMs: number; + requests: number; + success: number; + clientErrors: number; + serverErrors: number; + /** Non-2xx responses divided by total responses. */ + errorRate: number; + throughputRps: number; + latencyMs: LatencyStats; +} + +export interface LoadReportEnvironment { + node: string; + platform: string; + release: string; + cpus: number; + totalMemoryMb: number; +} + +export interface LoadReport { + schemaVersion: number; + generatedAt: string; + environment: LoadReportEnvironment; + scenarios: ScenarioResult[]; + totals: { + requests: number; + errorRate: number; + throughputRps: number; + latencyMs: LatencyStats; + }; + thresholds: LoadTestThresholds; + passed: boolean; + failures: string[]; +} + +/** One probe invocation: returns the HTTP status and how long it took. */ +export interface ProbeResult { + status: number; + durationMs: number; +} + +export type LoadTestProbe = (scenario: LoadTestScenario) => Promise; + +const EMPTY_STATS: LatencyStats = { min: 0, mean: 0, p50: 0, p90: 0, p95: 0, p99: 0, max: 0 }; + +/** + * Nearest-rank percentile (the same definition used by most HTTP load tools). + * `sorted` must be ascending. Returns 0 for an empty input. + */ +export function percentile(sorted: number[], p: number): number { + if (sorted.length === 0) return 0; + if (p <= 0) return sorted[0]; + if (p >= 100) return sorted[sorted.length - 1]; + const rank = Math.ceil((p / 100) * sorted.length); + const index = Math.min(sorted.length - 1, Math.max(0, rank - 1)); + return sorted[index]; +} + +/** Summary statistics for a set of latency samples. */ +export function summarizeLatencies(latencies: number[]): LatencyStats { + if (latencies.length === 0) return { ...EMPTY_STATS }; + const sorted = [...latencies].sort((a, b) => a - b); + const sum = sorted.reduce((total, value) => total + value, 0); + return { + min: round(sorted[0]), + mean: round(sum / sorted.length), + p50: round(percentile(sorted, 50)), + p90: round(percentile(sorted, 90)), + p95: round(percentile(sorted, 95)), + p99: round(percentile(sorted, 99)), + max: round(sorted[sorted.length - 1]), + }; +} + +function round(value: number): number { + return Math.round(value * 100) / 100; +} + +/** + * Runs one phase (warm-up or measured) at the scenario's concurrency. + * + * Each worker loops "check deadline/quota → issue request → await". Because the + * check and the counter increment happen in the same synchronous block, the + * `maxRequests` cap is never exceeded even with high concurrency. + */ +async function runPhase( + scenario: LoadTestScenario, + probe: LoadTestProbe, + durationMs: number, + maxRequests?: number, +): Promise<{ + latencies: number[]; + success: number; + clientErrors: number; + serverErrors: number; + elapsedMs: number; +}> { + const startedAt = Date.now(); + const deadline = startedAt + durationMs; + const latencies: number[] = []; + let success = 0; + let clientErrors = 0; + let serverErrors = 0; + let issued = 0; + + const workers = Array.from({ length: Math.max(1, scenario.concurrency) }, async () => { + while (Date.now() < deadline && (maxRequests === undefined || issued < maxRequests)) { + issued += 1; + const result = await probe(scenario); + latencies.push(result.durationMs); + if (result.status >= 200 && result.status < 300) success += 1; + else if (result.status >= 500) serverErrors += 1; + else clientErrors += 1; + } + }); + + await Promise.all(workers); + return { + latencies, + success, + clientErrors, + serverErrors, + elapsedMs: Math.max(1, Date.now() - startedAt), + }; +} + +/** Executes a single scenario (warm-up + measured phase) and summarises it. */ +export async function runScenario( + scenario: LoadTestScenario, + probe: LoadTestProbe, +): Promise { + if (scenario.warmupMs && scenario.warmupMs > 0) { + await runPhase(scenario, probe, scenario.warmupMs, scenario.maxRequests); + } + + const { latencies, success, clientErrors, serverErrors, elapsedMs } = await runPhase( + scenario, + probe, + scenario.durationMs, + scenario.maxRequests, + ); + + const requests = latencies.length; + return { + name: scenario.name, + method: scenario.method, + path: scenario.path, + concurrency: scenario.concurrency, + durationMs: elapsedMs, + requests, + success, + clientErrors, + serverErrors, + errorRate: requests === 0 ? 0 : round((requests - success) / requests), + throughputRps: round(requests / (elapsedMs / 1000)), + latencyMs: summarizeLatencies(latencies), + }; +} + +function describeEnvironment(): LoadReportEnvironment { + return { + node: process.version, + platform: os.platform(), + release: os.release(), + cpus: os.cpus().length, + totalMemoryMb: Math.round(os.totalmem() / (1024 * 1024)), + }; +} + +/** + * Runs every scenario sequentially and evaluates the configured thresholds. + * Scenarios run one after another so they don't contend for the same event loop + * / server capacity (which would make results non-comparable across runs). + */ +export async function runLoadTest( + config: LoadTestConfig, + probe: LoadTestProbe, +): Promise { + const scenarios: ScenarioResult[] = []; + for (const scenario of config.scenarios) { + scenarios.push(await runScenario(scenario, probe)); + } + + let requests = 0; + let success = 0; + let elapsedMs = 0; + for (const scenario of scenarios) { + requests += scenario.requests; + success += scenario.success; + elapsedMs += scenario.durationMs; + } + + const totals = { + requests, + errorRate: requests === 0 ? 0 : round((requests - success) / requests), + throughputRps: elapsedMs === 0 ? 0 : round(requests / (elapsedMs / 1000)), + latencyMs: summarizeLatencies(collectScenarioLatencies(scenarios)), + }; + const failures = evaluateThresholds(scenarios, totals, config.thresholds); + + return { + schemaVersion: LOAD_REPORT_SCHEMA_VERSION, + generatedAt: new Date().toISOString(), + environment: describeEnvironment(), + scenarios, + totals, + thresholds: config.thresholds, + passed: failures.length === 0, + failures, + }; +} + +/** + * We only keep per-scenario aggregate latency stats in the report, so the + * aggregate "latencyMs" is derived from those percentiles (a report-level + * roll-up, not a re-sampling of raw requests). + */ +function collectScenarioLatencies(scenarios: ScenarioResult[]): number[] { + const samples: number[] = []; + for (const scenario of scenarios) { + // Weight each scenario's percentile points by its request count so the + // roll-up tracks the overall traffic mix. + const weight = Math.max(1, Math.min(50, Math.round(scenario.requests / 10) || 1)); + for (let i = 0; i < weight; i++) { + samples.push( + scenario.latencyMs.p50, + scenario.latencyMs.p90, + scenario.latencyMs.p95, + scenario.latencyMs.p99, + ); + } + } + return samples; +} + +function evaluateThresholds( + scenarios: ScenarioResult[], + totals: LoadReport['totals'], + thresholds: LoadTestThresholds, +): string[] { + const failures: string[] = []; + + if (totals.errorRate > thresholds.maxErrorRate) { + failures.push( + `Aggregate error rate ${(totals.errorRate * 100).toFixed(2)}% exceeds maxErrorRate ${(thresholds.maxErrorRate * 100).toFixed(2)}%`, + ); + } + + for (const scenario of scenarios) { + if (scenario.latencyMs.p95 > thresholds.maxP95Ms) { + failures.push( + `Scenario "${scenario.name}" p95 ${scenario.latencyMs.p95}ms exceeds maxP95Ms ${thresholds.maxP95Ms}ms`, + ); + } + } + + if ( + thresholds.minThroughputRps !== undefined && + totals.throughputRps < thresholds.minThroughputRps + ) { + failures.push( + `Aggregate throughput ${totals.throughputRps} rps is below minThroughputRps ${thresholds.minThroughputRps}`, + ); + } + + return failures; +} + +export interface ScenarioComparison { + name: string; + status: 'added' | 'removed' | 'compared'; + throughputRps?: Delta; + p95Ms?: Delta; + errorRate?: Delta; +} + +export interface Delta { + baseline: number; + current: number; + /** Absolute change (current - baseline). */ + delta: number; + /** Relative change as a fraction (0.25 = +25%). */ + deltaPct: number; +} + +export interface LoadReportComparison { + scenarios: ScenarioComparison[]; + totals: { + throughputRps: Delta; + p95Ms: Delta; + errorRate: Delta; + }; + /** True when no scenario's p95 regressed by more than `regressionTolerancePct`. */ + passed: boolean; + regressions: string[]; +} + +function makeDelta(baseline: number, current: number): Delta { + const delta = round(current - baseline); + return { + baseline, + current, + delta, + deltaPct: baseline === 0 ? (current === 0 ? 0 : 1) : round((current - baseline) / baseline), + }; +} + +/** + * Diffs two reports. `regressionTolerancePct` is the acceptable relative p95 + * increase (0.1 = 10%); anything above it is reported as a regression. + */ +export function compareLoadReports( + baseline: LoadReport, + current: LoadReport, + regressionTolerancePct = 0.1, +): LoadReportComparison { + const baselineByName = new Map(baseline.scenarios.map((scenario) => [scenario.name, scenario])); + const currentByName = new Map(current.scenarios.map((scenario) => [scenario.name, scenario])); + const names = Array.from(new Set([...baselineByName.keys(), ...currentByName.keys()])); + + const scenarios: ScenarioComparison[] = []; + const regressions: string[] = []; + + for (const name of names) { + const before = baselineByName.get(name); + const after = currentByName.get(name); + if (before && !after) { + scenarios.push({ name, status: 'removed' }); + continue; + } + if (!before && after) { + scenarios.push({ name, status: 'added' }); + continue; + } + if (!before || !after) continue; + + const p95Ms = makeDelta(before.latencyMs.p95, after.latencyMs.p95); + scenarios.push({ + name, + status: 'compared', + throughputRps: makeDelta(before.throughputRps, after.throughputRps), + p95Ms, + errorRate: makeDelta(before.errorRate, after.errorRate), + }); + + if (p95Ms.deltaPct > regressionTolerancePct) { + regressions.push( + `Scenario "${name}" p95 regressed ${(p95Ms.deltaPct * 100).toFixed(1)}% (${before.latencyMs.p95}ms → ${after.latencyMs.p95}ms)`, + ); + } + } + + return { + scenarios, + totals: { + throughputRps: makeDelta(baseline.totals.throughputRps, current.totals.throughputRps), + p95Ms: makeDelta(baseline.totals.latencyMs.p95, current.totals.latencyMs.p95), + errorRate: makeDelta(baseline.totals.errorRate, current.totals.errorRate), + }, + passed: regressions.length === 0, + regressions, + }; +} + +/** Human-readable one-screen summary of a report. */ +export function formatLoadReport(report: LoadReport): string { + const lines: string[] = []; + lines.push(`Load report (${report.generatedAt}) — schema v${report.schemaVersion}`); + lines.push( + `Environment: node ${report.environment.node} · ${report.environment.platform} ${report.environment.release} · ${report.environment.cpus} cpus`, + ); + lines.push(''); + + const header = ['scenario', 'reqs', 'err%', 'rps', 'p50', 'p95', 'p99', 'max']; + const rows = report.scenarios.map((scenario) => [ + scenario.name, + String(scenario.requests), + `${(scenario.errorRate * 100).toFixed(2)}%`, + scenario.throughputRps.toFixed(1), + `${scenario.latencyMs.p50}ms`, + `${scenario.latencyMs.p95}ms`, + `${scenario.latencyMs.p99}ms`, + `${scenario.latencyMs.max}ms`, + ]); + // Size each column to its widest cell so long scenario names don't collide + // with the next column. + const widths = header.map( + (title, index) => Math.max(title.length, ...rows.map((row) => row[index].length)) + 2, + ); + const renderRow = (cells: string[]): string => + cells + .map((cell, index) => cell.padEnd(widths[index])) + .join('') + .trimEnd(); + + lines.push(renderRow(header)); + for (const row of rows) lines.push(renderRow(row)); + lines.push(''); + lines.push( + `Totals: ${report.totals.requests} requests · ${report.totals.throughputRps.toFixed(1)} rps · error rate ${(report.totals.errorRate * 100).toFixed(2)}% · p95 ${report.totals.latencyMs.p95}ms`, + ); + lines.push(report.passed ? 'Thresholds: PASS' : `Thresholds: FAIL (${report.failures.length})`); + for (const failure of report.failures) lines.push(` ✗ ${failure}`); + return lines.join('\n'); +} + +/** Human-readable summary of a baseline comparison. */ +export function formatComparison(comparison: LoadReportComparison): string { + const lines: string[] = []; + lines.push('Comparison vs baseline:'); + for (const scenario of comparison.scenarios) { + if (scenario.status !== 'compared') { + lines.push(` ${scenario.name}: ${scenario.status}`); + continue; + } + const p95 = scenario.p95Ms!; + const rps = scenario.throughputRps!; + const arrow = p95.deltaPct > 0 ? '↑' : '↓'; + lines.push( + ` ${scenario.name}: p95 ${p95.baseline}ms → ${p95.current}ms (${arrow}${(Math.abs(p95.deltaPct) * 100).toFixed(1)}%), rps ${rps.baseline.toFixed(1)} → ${rps.current.toFixed(1)}`, + ); + } + lines.push( + ` totals: p95 ${comparison.totals.p95Ms.baseline}ms → ${comparison.totals.p95Ms.current}ms, rps ${comparison.totals.throughputRps.baseline.toFixed(1)} → ${comparison.totals.throughputRps.current.toFixed(1)}`, + ); + if (comparison.regressions.length > 0) { + lines.push('Regressions:'); + for (const regression of comparison.regressions) lines.push(` ✗ ${regression}`); + } + return lines.join('\n'); +} From f6d40ba10def372c1b9da08221cda350e5f1c512 Mon Sep 17 00:00:00 2001 From: oluwaseyi1996-netizen Date: Tue, 29 Sep 2026 20:26:43 +0000 Subject: [PATCH 4/4] chore(listener): resync package-lock with package.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit listener/package-lock.json had drifted from package.json (it still pinned jest 29, typescript 5.4 and @types/node 25, and was missing @types/cors, @types/express, @types/node-cron and @types/winston), so `npm ci` failed with EUSAGE before installing anything. Regenerate the lock file so a clean install is possible again. 🤖 Generated with Codebuff Co-Authored-By: Codebuff --- listener/package-lock.json | 4728 +++++++++++++++++++++++------------- 1 file changed, 3039 insertions(+), 1689 deletions(-) diff --git a/listener/package-lock.json b/listener/package-lock.json index 818596fc..19bebe3f 100644 --- a/listener/package-lock.json +++ b/listener/package-lock.json @@ -20,18 +20,22 @@ "devDependencies": { "@swc/core": "^1.15.41", "@swc/jest": "^0.2.39", - "@types/jest": "^29.5.14", - "@types/node": "^25.9.3", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.6", + "@types/jest": "^30.0.0", + "@types/node": "^26.1.1", "@types/node-cache": "^4.1.3", + "@types/node-cron": "^3.0.11", "@types/uuid": "^9.0.8", + "@types/winston": "^2.4.4", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.10.0", "eslint": "^8.46.0", - "jest": "^29.7.0", + "jest": "^30.4.2", "prettier": "3.3.3", - "ts-jest": "^29.4.11", + "ts-jest": "^29.4.12", "ts-node": "^10.9.2", - "typescript": "~5.4.5" + "typescript": "~5.7.2" } }, "node_modules/@babel/code-frame": { @@ -86,23 +90,6 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/@babel/generator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/core/node_modules/@babel/helpers": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", @@ -150,6 +137,37 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", "dev": true, @@ -185,6 +203,39 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-module-imports/node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-transforms": { "version": "7.29.7", "dev": true, @@ -201,6 +252,39 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-plugin-utils": { "version": "7.29.7", "dev": true, @@ -346,6 +430,8 @@ }, "node_modules/@babel/plugin-syntax-jsx": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { @@ -454,6 +540,8 @@ }, "node_modules/@babel/plugin-syntax-typescript": { "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { @@ -493,14 +581,10 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse": { - "dev": true - }, - "node_modules/@babel/types": { - "dev": true - }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, "license": "MIT" }, @@ -540,6 +624,40 @@ "kuler": "^2.0.0" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.10.1", "dev": true, @@ -623,6 +741,109 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "license": "ISC", @@ -635,6 +856,8 @@ }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", "dependencies": { @@ -648,16 +871,10 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -668,22 +885,10 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.15.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", - "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -695,6 +900,8 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -709,6 +916,8 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -720,6 +929,8 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -735,52 +946,108 @@ } }, "node_modules/@jest/console": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.2.tgz", + "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.5.1", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/console/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/@jest/core": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.2.tgz", + "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/console": "30.5.2", + "@jest/pattern": "30.5.0", + "@jest/reporters": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.5.1", + "jest-config": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-resolve-dependencies": "30.5.2", + "jest-runner": "30.5.2", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "jest-watcher": "30.5.2", + "pretty-format": "30.5.1", + "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@jest/console/node_modules/@sinclair/typebox": { - "version": "0.27.12", + "node_modules/@jest/core/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } }, "node_modules/@jest/create-cache-key-function": { "version": "30.5.0", @@ -793,169 +1060,160 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/environment": { - "version": "29.7.0", + "node_modules/@jest/diff-sequences": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz", + "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/@jest/environment": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz", + "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-mock": "30.5.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/environment/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, "node_modules/@jest/expect": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "expect": "30.5.2", + "jest-snapshot": "30.5.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.2.tgz", + "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/get-type": "30.5.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz", + "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", + "@jest/types": "30.5.1", + "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/globals": { - "version": "29.7.0", + "node_modules/@jest/get-type": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz", + "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/@jest/globals": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.2.tgz", + "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/types": "30.5.1", + "jest-mock": "30.5.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/globals/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, "node_modules/@jest/pattern": { "version": "30.5.0", "dev": true, @@ -968,38 +1226,55 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/react-is-19": { + "name": "react-is", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.0.tgz", + "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/reporters": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.2.tgz", + "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1010,37 +1285,95 @@ } } }, - "node_modules/@jest/reporters/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/reporters/node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/@jest/reporters/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "20 || >=22" } }, - "node_modules/@jest/reporters/node_modules/@sinclair/typebox": { - "version": "0.27.12", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "MIT" + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/@jest/schemas": { "version": "30.5.0", @@ -1060,142 +1393,151 @@ "dev": true, "license": "MIT" }, - "node_modules/@jest/source-map": { - "version": "29.6.3", + "node_modules/@jest/snapshot-utils": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz", + "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jest/types": "30.5.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/test-result": { - "version": "29.7.0", + "node_modules/@jest/snapshot-utils/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/test-result/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/@jest/source-map": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.2.tgz", + "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jridgewell/trace-mapping": "^0.3.31", + "callsites": "^3.1.0", + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/test-result/node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/@jest/test-result": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.2.tgz", + "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@jest/console": "30.5.2", + "@jest/types": "30.5.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/test-result/node_modules/@sinclair/typebox": { - "version": "0.27.12", + "node_modules/@jest/test-result/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } }, "node_modules/@jest/test-sequencer": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.2.tgz", + "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", + "@jest/test-result": "30.5.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz", + "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", + "@babel/core": "^7.27.4", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "babel-plugin-istanbul": "^8.0.0", + "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/transform/node_modules/jest-regex-util": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { @@ -1255,6 +1597,28 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -1287,206 +1651,225 @@ "node": ">= 8" } }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@so-ric/colorspace": { - "version": "1.1.6", - "license": "MIT", - "dependencies": { - "color": "^5.0.2", - "text-hex": "1.0.x" - } - }, - "node_modules/@swc/core": { - "version": "1.16.1", + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", "dev": true, "hasInstallScript": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.28" + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=10" + "node": ">= 10.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/swc" + "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.16.1", - "@swc/core-darwin-x64": "1.16.1", - "@swc/core-linux-arm-gnueabihf": "1.16.1", - "@swc/core-linux-arm64-gnu": "1.16.1", - "@swc/core-linux-arm64-musl": "1.16.1", - "@swc/core-linux-ppc64-gnu": "1.16.1", - "@swc/core-linux-s390x-gnu": "1.16.1", - "@swc/core-linux-x64-gnu": "1.16.1", - "@swc/core-linux-x64-musl": "1.16.1", - "@swc/core-win32-arm64-msvc": "1.16.1", - "@swc/core-win32-ia32-msvc": "1.16.1", - "@swc/core-win32-x64-msvc": "1.16.1" - }, - "peerDependencies": { - "@swc/helpers": ">=0.5.17" + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.1.tgz", - "integrity": "sha512-zlJblJ8ncErD43lKdxjbUaUskJQf+LxiPXYcWXD8/8ZMV+7uuAT+CwjciLXpyZBd5Pq/S726bMpeeAwSeL1hhg==", + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.16.1.tgz", - "integrity": "sha512-IN0BmPWb0YAh/17mmlWB/HDBtTw2MfuW4hulf/tQAgTQBRH17l+z499bNJLK6LizSjqs0P7V+jU38Zj+vJC1DA==", + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=10" - } + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.1.tgz", - "integrity": "sha512-EYgrx2YOCQ2Twz2S793kqNjPkpvYVUPzzR95bIb7by+VQcyaai4lZZ2iz/tZvcFVKSNcN3/JTKwx+aBn2ZL52A==", + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", "cpu": [ - "arm" + "x64" ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "freebsd" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.1.tgz", - "integrity": "sha512-moyKm0YZlHdHohzm1YwgAyesqnE853rO0REMfJLFAova51wF9BNi+3ZW2PeS7Vqvn6HeJuepLpAHbBdZctxpHA==", + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", "cpu": [ - "arm64" + "arm" ], "dev": true, "libc": [ "glibc" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.1.tgz", - "integrity": "sha512-kKGBO9wdapiSzuf5ZzZ2fYtlu1BNSYtIIUxvH1ir/gcelTOREEHGDCLTDFx/2Knf878nU11A40z7LxwasEFxqA==", + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", "cpu": [ - "arm64" + "arm" ], "dev": true, "libc": [ "musl" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.1.tgz", - "integrity": "sha512-nZ6qahtLxC3PM54cWOQZHxt4lTCF/3J4LIoWWzz6v7A+rLs8Dx54anYQf7mH3eIi8KlNpgKci/ie8ZSqFN8O7A==", + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", "cpu": [ - "ppc64" + "arm64" ], "dev": true, "libc": [ "glibc" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.1.tgz", - "integrity": "sha512-4ji5PNzhYq193Z4/4xUaSoNJza6iCkDJSzhetrbB6KOYxsr+kxtQr8ePWhMJUiMt6JUWtXaZ1PYT8FhtED+nGA==", + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "libc": [ - "glibc" + "musl" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.1.tgz", - "integrity": "sha512-VJQxqrisHV+B394IgrOu8YsIIXZgffnf5tO+yc9Z/hoUpuZEvuQTjWwlnpZdpyD+0nx6LTD1/3k646JYm43yJA==", + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", "cpu": [ "x64" ], @@ -1494,19 +1877,23 @@ "libc": [ "glibc" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.1.tgz", - "integrity": "sha512-r9oV1mwxxsIGcLV1IQ/tw76MW3doatKze1QFWuC+a7QqJUkhY/bKTSVk6NpKKUGm2LDsE33Va8VqSClfA7vSiQ==", + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", "cpu": [ "x64" ], @@ -1514,214 +1901,616 @@ "libc": [ "musl" ], - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.1.tgz", - "integrity": "sha512-6huNRessoBLxWEqBm5zJXyCQ27TO7anvkdiuQ5MDO4CJni0nOXEqKtV9RllQ2TdyENKKsUMXVnIfW2hIXx/R5Q==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", "cpu": [ "arm64" ], "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.1.tgz", - "integrity": "sha512-OVKJFUzphrGmsh+BGtcZDesx0YryV7/Yvy5XGgTqnrZfjnyfcr5uaqYQugCckdIlupc5Vs3XtDjRAj12z4ZPlw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=10" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.16.1", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", "cpu": [ "x64" ], "dev": true, - "license": "Apache-2.0 AND MIT", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@swc/jest": { - "version": "0.2.39", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/create-cache-key-function": "^30.0.0", - "@swc/counter": "^0.1.3", - "jsonc-parser": "^3.2.0" - }, - "engines": { - "npm": ">= 7.0.0" + "node": ">= 10.0.0" }, - "peerDependencies": { - "@swc/core": "*" - } - }, - "node_modules/@swc/types": { - "version": "0.1.28", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.13", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", + "node_modules/@parcel/watcher/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "dev": true, "license": "MIT" }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__core/node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" + "optional": true, + "engines": { + "node": ">=14" } }, - "node_modules/@types/babel__generator/node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "type-detect": "4.0.8" } }, - "node_modules/@types/babel__template/node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "node_modules/@sinonjs/fake-timers": { + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "dev": true, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "color": "^5.0.2", + "text-hex": "1.0.x" } }, - "node_modules/@types/babel__traverse/node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "node_modules/@swc/core": { + "version": "1.16.1", "dev": true, - "license": "MIT", + "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.28" }, "engines": { - "node": ">=6.9.0" - } + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.16.1", + "@swc/core-darwin-x64": "1.16.1", + "@swc/core-linux-arm-gnueabihf": "1.16.1", + "@swc/core-linux-arm64-gnu": "1.16.1", + "@swc/core-linux-arm64-musl": "1.16.1", + "@swc/core-linux-ppc64-gnu": "1.16.1", + "@swc/core-linux-s390x-gnu": "1.16.1", + "@swc/core-linux-x64-gnu": "1.16.1", + "@swc/core-linux-x64-musl": "1.16.1", + "@swc/core-win32-arm64-msvc": "1.16.1", + "@swc/core-win32-ia32-msvc": "1.16.1", + "@swc/core-win32-x64-msvc": "1.16.1" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.16.1.tgz", + "integrity": "sha512-zlJblJ8ncErD43lKdxjbUaUskJQf+LxiPXYcWXD8/8ZMV+7uuAT+CwjciLXpyZBd5Pq/S726bMpeeAwSeL1hhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.16.1.tgz", + "integrity": "sha512-IN0BmPWb0YAh/17mmlWB/HDBtTw2MfuW4hulf/tQAgTQBRH17l+z499bNJLK6LizSjqs0P7V+jU38Zj+vJC1DA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.16.1.tgz", + "integrity": "sha512-EYgrx2YOCQ2Twz2S793kqNjPkpvYVUPzzR95bIb7by+VQcyaai4lZZ2iz/tZvcFVKSNcN3/JTKwx+aBn2ZL52A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.16.1.tgz", + "integrity": "sha512-moyKm0YZlHdHohzm1YwgAyesqnE853rO0REMfJLFAova51wF9BNi+3ZW2PeS7Vqvn6HeJuepLpAHbBdZctxpHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.16.1.tgz", + "integrity": "sha512-kKGBO9wdapiSzuf5ZzZ2fYtlu1BNSYtIIUxvH1ir/gcelTOREEHGDCLTDFx/2Knf878nU11A40z7LxwasEFxqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.16.1.tgz", + "integrity": "sha512-nZ6qahtLxC3PM54cWOQZHxt4lTCF/3J4LIoWWzz6v7A+rLs8Dx54anYQf7mH3eIi8KlNpgKci/ie8ZSqFN8O7A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.16.1.tgz", + "integrity": "sha512-4ji5PNzhYq193Z4/4xUaSoNJza6iCkDJSzhetrbB6KOYxsr+kxtQr8ePWhMJUiMt6JUWtXaZ1PYT8FhtED+nGA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.16.1.tgz", + "integrity": "sha512-VJQxqrisHV+B394IgrOu8YsIIXZgffnf5tO+yc9Z/hoUpuZEvuQTjWwlnpZdpyD+0nx6LTD1/3k646JYm43yJA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.16.1.tgz", + "integrity": "sha512-r9oV1mwxxsIGcLV1IQ/tw76MW3doatKze1QFWuC+a7QqJUkhY/bKTSVk6NpKKUGm2LDsE33Va8VqSClfA7vSiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.16.1.tgz", + "integrity": "sha512-6huNRessoBLxWEqBm5zJXyCQ27TO7anvkdiuQ5MDO4CJni0nOXEqKtV9RllQ2TdyENKKsUMXVnIfW2hIXx/R5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.16.1.tgz", + "integrity": "sha512-OVKJFUzphrGmsh+BGtcZDesx0YryV7/Yvy5XGgTqnrZfjnyfcr5uaqYQugCckdIlupc5Vs3XtDjRAj12z4ZPlw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.16.1", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/jest": { + "version": "0.2.39", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^30.0.0", + "@swc/counter": "^0.1.3", + "jsonc-parser": "^3.2.0" + }, + "engines": { + "npm": ">= 7.0.0" + }, + "peerDependencies": { + "@swc/core": "*" + } + }, + "node_modules/@swc/types": { + "version": "0.1.28", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__core/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__generator/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/babel__traverse/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "dev": true, @@ -1744,12 +2533,14 @@ } }, "node_modules/@types/jest": { - "version": "29.5.14", + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" + "expect": "^30.0.0", + "pretty-format": "^30.0.0" } }, "node_modules/@types/json-schema": { @@ -1758,13 +2549,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", - "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", + "version": "26.6.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", + "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.9.0" } }, "node_modules/@types/node-cache": { @@ -1775,13 +2566,57 @@ "@types/node": "*" } }, + "node_modules/@types/node-cron": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.11.tgz", + "integrity": "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/semver": { "version": "7.8.0", "dev": true, "license": "MIT" }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true, "license": "MIT" }, @@ -1794,6 +2629,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/winston": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-BVGCztsypW8EYwJ+Hq+QNYiT/MUyCif0ouBH+flrY66O5W+KIXAMML6E/0fJpm7VjIzgangahl5S03bJJQGrZw==", + "deprecated": "This is a stub types definition. winston provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "winston": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.35", "dev": true, @@ -1859,13 +2705,99 @@ "node_modules/@typescript-eslint/parser": { "version": "6.21.0", "dev": true, - "license": "BSD-2-Clause", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.21.0", + "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" + "@typescript-eslint/utils": "6.21.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1883,7 +2815,7 @@ } } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", @@ -1912,7 +2844,7 @@ } } }, - "node_modules/@typescript-eslint/parser/node_modules/semver": { + "node_modules/@typescript-eslint/type-utils/node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", @@ -1925,16 +2857,10 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/scope-manager": { + "node_modules/@typescript-eslint/types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" - }, "engines": { "node": "^16.0.0 || >=18.0.0" }, @@ -1943,15 +2869,20 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { + "node_modules/@typescript-eslint/utils": { "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "license": "MIT", "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "semver": "^7.5.4" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1962,42 +2893,57 @@ }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/types": { + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, "engines": { "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/typescript-estree": { - "dev": true + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/@typescript-eslint/utils": { + "node_modules/@typescript-eslint/visitor-keys": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2005,73 +2951,355 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "dev": true, + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=14.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^16.0.0 || >=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@ungap/structured-clone": { - "version": "1.4.0", + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/abbrev": { "version": "4.0.0", @@ -2124,6 +3352,8 @@ }, "node_modules/ansi-escapes": { "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2138,6 +3368,8 @@ }, "node_modules/ansi-escapes/node_modules/type-fest": { "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -2206,81 +3438,58 @@ "license": "MIT" }, "node_modules/babel-jest": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz", + "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.5.2", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^8.0.0", + "babel-preset-jest": "30.5.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz", + "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^7.0.1" }, "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-plugin-jest-hoist/node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz", + "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": ">=6.9.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -2309,18 +3518,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz", + "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.5.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0" } }, "node_modules/balanced-match": { @@ -2473,6 +3684,27 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/browserslist/node_modules/caniuse-lite": { + "version": "1.0.30001813", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001813.tgz", + "integrity": "sha512-zfjJo4rM0+fUomGDBW/xcDjhIwz/210DGvip2MAMDZ8KHcRPnOHmEgHPZP0UHlZoxr8fYKVJOqcORhYQcG4FKQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/bs-logger": { "version": "0.2.6", "dev": true, @@ -2514,11 +3746,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, "node_modules/bytes": { "version": "3.1.2", "license": "MIT", @@ -2561,15 +3788,14 @@ }, "node_modules/camelcase": { "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/caniuse-lite": { - "dev": true - }, "node_modules/chalk": { "version": "4.1.2", "dev": true, @@ -2587,6 +3813,8 @@ }, "node_modules/char-regex": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", "engines": { @@ -2601,7 +3829,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -2615,12 +3845,16 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.3", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz", + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", "dev": true, "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2634,6 +3868,8 @@ }, "node_modules/co": { "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", "engines": { @@ -2643,6 +3879,8 @@ }, "node_modules/collect-v8-coverage": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, @@ -2764,72 +4002,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-jest/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-jest/node_modules/@jest/types": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-jest/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest/node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/create-require": { "version": "1.1.1", "dev": true, @@ -2878,6 +4050,8 @@ }, "node_modules/dedent": { "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2903,6 +4077,8 @@ }, "node_modules/deepmerge": { "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, "license": "MIT", "engines": { @@ -2925,6 +4101,8 @@ }, "node_modules/detect-newline": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", "engines": { @@ -2939,14 +4117,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "dev": true, @@ -2991,6 +4161,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "license": "MIT" @@ -3002,6 +4179,8 @@ }, "node_modules/emittery": { "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", "engines": { @@ -3013,6 +4192,8 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, @@ -3044,6 +4225,8 @@ }, "node_modules/error-ex": { "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3064,6 +4247,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.2", "license": "MIT", @@ -3286,6 +4476,8 @@ }, "node_modules/esprima": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -3343,6 +4535,8 @@ }, "node_modules/execa": { "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "license": "MIT", "dependencies": { @@ -3363,9 +4557,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } @@ -3378,18 +4575,21 @@ } }, "node_modules/expect": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/exponential-backoff": { @@ -3443,9 +4643,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "dev": true - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "dev": true, @@ -3558,6 +4755,36 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "license": "MIT", @@ -3581,21 +4808,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "license": "MIT", @@ -3613,6 +4825,8 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -3643,6 +4857,8 @@ }, "node_modules/get-package-type": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { @@ -3662,6 +4878,8 @@ }, "node_modules/get-stream": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", "engines": { @@ -3758,6 +4976,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/globby/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.2.0", "license": "MIT", @@ -3849,6 +5097,13 @@ "node": ">= 0.4" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.1", "license": "MIT", @@ -3869,6 +5124,8 @@ }, "node_modules/human-signals": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3918,6 +5175,8 @@ }, "node_modules/import-local": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", "dependencies": { @@ -3968,23 +5227,11 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, - "node_modules/is-core-module": { - "version": "2.16.2", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "dev": true, @@ -3995,6 +5242,8 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { @@ -4003,6 +5252,8 @@ }, "node_modules/is-generator-fn": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", "engines": { @@ -4065,6 +5316,8 @@ }, "node_modules/istanbul-lib-instrument": { "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4093,6 +5346,8 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4105,36 +5360,53 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/istanbul-reports": { - "dev": true + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } }, "node_modules/jest": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.2.tgz", + "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" + "@jest/core": "30.5.2", + "@jest/types": "30.5.1", + "import-local": "^3.2.0", + "jest-cli": "30.5.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -4146,167 +5418,94 @@ } }, "node_modules/jest-changed-files": { - "version": "29.7.0", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.5.1.tgz", + "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", + "execa": "^5.1.1", + "jest-util": "30.5.1", "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.2.tgz", + "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", + "pretty-format": "30.5.1", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-circus/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-circus/node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, "node_modules/jest-cli": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.2.tgz", + "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "@jest/core": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -4317,730 +5516,684 @@ } } }, - "node_modules/jest-cli/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-cli/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-cli/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": ">=12" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-config": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.2.tgz", + "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", + "@babel/core": "^7.27.4", + "@jest/get-type": "30.5.0", + "@jest/pattern": "30.5.0", + "@jest/test-sequencer": "30.5.2", + "@jest/types": "30.5.1", + "babel-jest": "30.5.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "jest-circus": "30.5.2", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-runner": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "30.5.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, - "node_modules/jest-config/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-config/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-config/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-config/node_modules/jest-regex-util": { - "version": "29.6.3", + "node_modules/jest-config/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/jest-diff": { - "version": "29.7.0", + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "20 || >=22" } }, - "node_modules/jest-docblock": { - "version": "29.7.0", + "node_modules/jest-config/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "detect-newline": "^3.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-each": { - "version": "29.7.0", + "node_modules/jest-config/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "brace-expansion": "^5.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-each/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/jest-diff": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.2.tgz", + "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/diff-sequences": "30.5.0", + "@jest/get-type": "30.5.0", + "chalk": "^4.1.2", + "pretty-format": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-each/node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/jest-docblock": { + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.5.0.tgz", + "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-each/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", + "node_modules/jest-each": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.2.tgz", + "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", + "chalk": "^4.1.2", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "pretty-format": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-environment-node/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/jest-each/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-environment-node/node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/jest-environment-node": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz", + "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "jest-mock": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-haste-map": { - "version": "29.7.0", + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-haste-map/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/jest-haste-map": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz", + "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/types": "30.5.1", + "@parcel/watcher": "^2.6.0", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "fdir": "^6.5.0", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "picomatch": "^4.0.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-haste-map/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-haste-map/node_modules/jest-regex-util": { - "version": "29.6.3", + "node_modules/jest-haste-map/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "node": ">=12.0.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "peerDependencies": { + "picomatch": "^3 || ^4" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/jest-message-util": { - "version": "29.7.0", + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" + "node": ">=12" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-message-util/node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/jest-leak-detector": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz", + "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@jest/get-type": "30.5.0", + "pretty-format": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-message-util/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-mock": { - "version": "29.7.0", + "node_modules/jest-matcher-utils": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.2.tgz", + "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" + "@jest/get-type": "30.5.0", + "chalk": "^4.1.2", + "jest-diff": "30.5.2", + "pretty-format": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/jest-message-util": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz", + "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.5.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.5.1", + "picomatch": "^4.0.3", + "pretty-format": "30.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/jest-message-util/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-mock/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" + "node": ">=12" }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "30.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-resolve": { - "version": "29.7.0", + "node_modules/jest-mock": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz", + "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "@jest/expect-utils": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "jest-util": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { - "version": "29.6.3", + "node_modules/jest-regex-util": { + "version": "30.5.0", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-resolve/node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "node_modules/jest-resolve": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.5.1.tgz", + "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "slash": "^3.0.0", + "unrs-resolver": "^1.12.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner": { - "version": "29.7.0", + "node_modules/jest-resolve-dependencies": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.2.tgz", + "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "jest-regex-util": "30.5.0", + "jest-snapshot": "30.5.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/jest-runner": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.2.tgz", + "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/console": "30.5.2", + "@jest/environment": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@types/node": "*", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-leak-detector": "30.5.1", + "jest-message-util": "30.5.1", + "jest-resolve": "30.5.1", + "jest-runtime": "30.5.2", + "jest-util": "30.5.1", + "jest-watcher": "30.5.2", + "jest-worker": "30.5.1", + "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, "node_modules/jest-runtime": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.2.tgz", + "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/globals": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.2.0", + "collect-v8-coverage": "^1.0.2", + "es-module-lexer": "^2.1.0", + "glob": "^13.0.6", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/@jest/types": { - "version": "29.6.3", + "node_modules/jest-runtime/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "18 || 20 || >=22" } }, - "node_modules/jest-runtime/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-runtime/node_modules/jest-regex-util": { - "version": "29.6.3", + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "20 || >=22" } }, - "node_modules/jest-snapshot": { - "version": "29.7.0", + "node_modules/jest-runtime/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jest-snapshot/node_modules/@babel/generator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "brace-expansion": "^5.0.8" }, "engines": { - "node": ">=6.9.0" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-snapshot": { + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.2.tgz", + "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "@jest/snapshot-utils": "30.5.1", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.5.2", + "graceful-fs": "^4.2.11", + "jest-diff": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "pretty-format": "30.5.1", + "semver": "^7.7.2", + "synckit": "^0.11.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot/node_modules/@babel/types": { @@ -5057,38 +6210,25 @@ "node": ">=6.9.0" } }, - "node_modules/jest-snapshot/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest-snapshot/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-snapshot/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, "node_modules/jest-snapshot/node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -5103,103 +6243,96 @@ } }, "node_modules/jest-util": { - "version": "29.7.0", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.5.1", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-util/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-util/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-validate": { - "version": "29.7.0", + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-validate/node_modules/@jest/schemas": { - "version": "29.6.3", + "node_modules/jest-validate": { + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.5.1.tgz", + "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.5.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-validate/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", "engines": { @@ -5210,71 +6343,65 @@ } }, "node_modules/jest-watcher": { - "version": "29.7.0", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.2.tgz", + "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-watcher/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" + "jest-util": "30.5.1", + "string-length": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-watcher/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-watcher/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, "node_modules/jest-worker": { - "version": "29.7.0", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz", + "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.7.0", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.5.1", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5287,100 +6414,53 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jest/node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest/node_modules/@jest/schemas": { - "version": "29.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/jest/node_modules/@jest/types": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest/node_modules/@sinclair/typebox": { - "version": "0.27.12", + "node_modules/js-tokens": { + "version": "4.0.0", "dev": true, "license": "MIT" }, - "node_modules/jest/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "node_modules/js-yaml": { + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/js-tokens": { - "version": "4.0.0", + "node_modules/js-yaml/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } }, "node_modules/jsesc": { "version": "3.1.0", @@ -5400,6 +6480,8 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, @@ -5437,20 +6519,14 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/kuler": { "version": "2.0.0", "license": "MIT" }, "node_modules/leven": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "license": "MIT", "engines": { @@ -5471,6 +6547,8 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true, "license": "MIT" }, @@ -5525,6 +6603,8 @@ }, "node_modules/make-dir": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -5555,14 +6635,6 @@ "dev": true, "license": "ISC" }, - "node_modules/makeerror": { - "version": "1.0.12", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "license": "MIT", @@ -5639,6 +6711,8 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { @@ -5705,6 +6779,22 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "dev": true, @@ -5882,6 +6972,8 @@ }, "node_modules/npm-run-path": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "license": "MIT", "dependencies": { @@ -5924,6 +7016,8 @@ }, "node_modules/onetime": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { @@ -5982,12 +7076,21 @@ }, "node_modules/p-try": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "dev": true, @@ -6001,6 +7104,8 @@ }, "node_modules/parse-json": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -6047,10 +7152,32 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, - "license": "MIT" + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-to-regexp": { "version": "8.4.2", @@ -6094,6 +7221,8 @@ }, "node_modules/pkg-dir": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6105,6 +7234,8 @@ }, "node_modules/pkg-dir/node_modules/find-up": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -6117,6 +7248,8 @@ }, "node_modules/pkg-dir/node_modules/locate-path": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -6128,6 +7261,8 @@ }, "node_modules/pkg-dir/node_modules/p-limit": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -6142,6 +7277,8 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -6200,36 +7337,25 @@ } }, "node_modules/pretty-format": { - "version": "29.7.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/@jest/schemas": { - "version": "29.6.3", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/pretty-format/node_modules/@sinclair/typebox": { - "version": "0.27.12", - "dev": true, - "license": "MIT" - }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", "engines": { @@ -6274,6 +7400,23 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.15.3", "license": "BSD-3-Clause", @@ -6367,11 +7510,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-is": { - "version": "18.3.1", - "dev": true, - "license": "MIT" - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -6388,6 +7526,8 @@ }, "node_modules/require-directory": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -6396,6 +7536,8 @@ }, "node_modules/resolve-cwd": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", "dependencies": { @@ -6407,6 +7549,8 @@ }, "node_modules/resolve-cwd/node_modules/resolve-from": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { @@ -6421,14 +7565,6 @@ "node": ">=4" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/reusify": { "version": "1.1.0", "dev": true, @@ -6749,11 +7885,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, "node_modules/slash": { "version": "3.0.0", "dev": true, @@ -6770,17 +7901,10 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.13", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, @@ -6824,6 +7948,8 @@ }, "node_modules/stack-utils": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6835,6 +7961,8 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", "engines": { @@ -6857,6 +7985,8 @@ }, "node_modules/string-length": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6869,6 +7999,24 @@ }, "node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -6891,8 +8039,24 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { @@ -6901,6 +8065,8 @@ }, "node_modules/strip-final-newline": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "license": "MIT", "engines": { @@ -6929,15 +8095,20 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", + "node_modules/synckit": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", "dev": true, "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, "engines": { - "node": ">= 0.4" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/synckit" } }, "node_modules/tar": { @@ -6994,36 +8165,119 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", "dev": true, "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^10.2.2" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.18", + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", + "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/text-hex": { @@ -7077,11 +8331,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/to-regex-range": { "version": "5.0.1", "dev": true, @@ -7239,6 +8488,14 @@ } } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/tunnel-agent": { "version": "0.6.0", "license": "Apache-2.0", @@ -7262,6 +8519,8 @@ }, "node_modules/type-detect": { "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "license": "MIT", "engines": { @@ -7307,9 +8566,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -7321,7 +8580,9 @@ } }, "node_modules/undici-types": { - "version": "7.24.6", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, @@ -7332,6 +8593,44 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, "node_modules/update-browserslist-db": { "version": "1.3.2", "dev": true, @@ -7380,6 +8679,8 @@ }, "node_modules/v8-to-istanbul": { "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", "dependencies": { @@ -7398,14 +8699,6 @@ "node": ">= 0.8" } }, - "node_modules/walker": { - "version": "1.0.8", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/which": { "version": "2.0.2", "dev": true, @@ -7469,6 +8762,27 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7488,19 +8802,36 @@ "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "4.0.2", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/y18n": { "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -7512,6 +8843,25 @@ "dev": true, "license": "ISC" }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yargs-parser": { "version": "21.1.1", "dev": true,