From 582a8e6267c37939f6f049a026cbcf7bd26c49c7 Mon Sep 17 00:00:00 2001 From: phertyameen Date: Sat, 21 Feb 2026 14:58:47 +0100 Subject: [PATCH 1/2] feat(api): add rate-limited request layer with retry and circuit breaker (#64) --- docs/Bridgewiseratelimit.html | 584 ++++++++++++++++++ .../adapters/stellar/src/rateLimitedApi.js | 408 ++++++++++++ .../stellar/src/rateLimitedApi.test.js | 236 +++++++ 3 files changed, 1228 insertions(+) create mode 100644 docs/Bridgewiseratelimit.html create mode 100644 packages/adapters/stellar/src/rateLimitedApi.js create mode 100644 packages/adapters/stellar/src/rateLimitedApi.test.js diff --git a/docs/Bridgewiseratelimit.html b/docs/Bridgewiseratelimit.html new file mode 100644 index 0000000..444969e --- /dev/null +++ b/docs/Bridgewiseratelimit.html @@ -0,0 +1,584 @@ + + + + + +BridgeWise · Rate-Limited API Layer · Issue #64 + + + +
+ + +
+
MDTechLabs / BridgeWise · Issue #64
+

Rate-Limited API Layer
& Retry Mechanism

+

Handles rate limits, transient failures, and API unreliability for bridge quotes, network fees, and liquidity data — without degrading UX.

+
+ + +
+
Architecture Overview
+
+
+
+
+ Quote Refresh
#17 +
+
CALLER
+
+
+
+
+
🪣
+ Token
Bucket +
+
RATE LIMITER
+
+
+
+
+
📋
+ Request
Queue +
+
CONCURRENCY
+
+
+
+
+
+ Circuit
Breaker +
+
FAULT GUARD
+
+
+
+
+
🔄
+ Retry +
Backoff +
+
RESILIENCE
+
+
+
+
+
🌐
+ External
APIs +
+
UPSTREAM
+
+
+
+ + +
+

Live Simulator INTERACTIVE

+ +
+
+
0
+
Requests
+
+
+
0
+
Success
+
+
+
0
+
Retried
+
+
+
0
+
Failed
+
+
+
0
+
Rate Limit Hits
+
+
+
+
+
+ CLOSED +
+
+
Circuit
+
+
+ +
+ + + + + + + +
+ +
+
+ 00:00:00 + [READY] + BridgeWise Rate-Limited API Layer initialised. Select a scenario above. +
+
+
+ + +
+

Usage

+
// 1. Import the shared client or domain helpers +import { fetchBridgeQuote, fetchNetworkFees, fetchLiquidityData } from './rateLimitedApi.js'; + +// 2. Use exactly like before — rate limiting & retry are transparent +const quote = await fetchBridgeQuote({ fromChain: 'eth', toChain: 'arb', token: 'USDC', amount: '1000' }); +const fees = await fetchNetworkFees('ethereum'); +const liq = await fetchLiquidityData({ pair: 'USDC/ETH' }); + +// 3. Or use the client directly for custom endpoints +import { sharedClient } from './rateLimitedApi.js'; + +const res = await sharedClient.get('/api/slippage', {}, { group: 'liquidity' }); +const data = await res.json(); + +// 4. Inspect metrics anytime +console.log(sharedClient.getMetrics()); +// { totalRequests: 42, successfulRequests: 40, retriedRequests: 5, rateLimitHits: 3, … }
+
+ + +
+

Configuration Reference

+
+ + + + + + + + + + +
OptionDefaultDescription
maxRetries3Maximum retry attempts per request
baseDelay1000 msBase delay for exponential backoff
maxDelay30 000 msCap on computed backoff delay
jittertrueFull jitter to avoid thundering herd
rateLimitPerSecond5Token bucket capacity per group
timeout10 000 msAbortController timeout per request
retryStatusCodes[429,500,502,503,504]HTTP codes triggering a retry
concurrency10Max parallel in-flight requests (queue)
+
+
+ +
+ MDTechLabs / BridgeWise · Issue #64 · Rate-Limited API Layer + rateLimitedApi.js · MIT +
+
+ + + + \ No newline at end of file diff --git a/packages/adapters/stellar/src/rateLimitedApi.js b/packages/adapters/stellar/src/rateLimitedApi.js new file mode 100644 index 0000000..8073564 --- /dev/null +++ b/packages/adapters/stellar/src/rateLimitedApi.js @@ -0,0 +1,408 @@ +/** + * BridgeWise - Rate-Limited API Handling Layer + * Issue #64: Implements rate limiting, retry logic, and request queuing + */ + +// ─── Configuration ──────────────────────────────────────────────────────────── + +const DEFAULT_CONFIG = { + maxRetries: 3, + baseDelay: 1000, // ms — base delay for exponential backoff + maxDelay: 30000, // ms — cap on backoff delay + jitter: true, // randomise delay to avoid thundering herd + rateLimitPerSecond: 5, // max requests per second per endpoint group + rateLimitPerMinute: 100, + timeout: 10000, // ms — per-request timeout + retryStatusCodes: [429, 500, 502, 503, 504], +}; + +// ─── Utilities ──────────────────────────────────────────────────────────────── + +/** + * Sleep for `ms` milliseconds. + */ +const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + +/** + * Compute exponential backoff delay with optional jitter. + * delay = min(baseDelay * 2^attempt, maxDelay) ± jitter + */ +function computeBackoffDelay(attempt, config) { + const exponential = Math.min( + config.baseDelay * Math.pow(2, attempt), + config.maxDelay + ); + if (!config.jitter) return exponential; + // Full jitter: random value in [0, exponential] + return Math.floor(Math.random() * exponential); +} + +/** + * Extract Retry-After header value (seconds) from a Response, if present. + * Returns null if not available. + */ +function parseRetryAfter(response) { + const header = response?.headers?.get?.("Retry-After"); + if (!header) return null; + const seconds = Number(header); + if (!isNaN(seconds)) return seconds * 1000; // convert to ms + const date = new Date(header); + if (!isNaN(date)) return Math.max(0, date - Date.now()); + return null; +} + +// ─── Token Bucket Rate Limiter ──────────────────────────────────────────────── + +class TokenBucket { + /** + * @param {number} capacity – max tokens (burst size) + * @param {number} refillRate – tokens added per second + */ + constructor(capacity, refillRate) { + this.capacity = capacity; + this.refillRate = refillRate; // tokens / ms + this.tokens = capacity; + this.lastRefill = Date.now(); + } + + _refill() { + const now = Date.now(); + const elapsed = now - this.lastRefill; + const added = elapsed * (this.refillRate / 1000); + this.tokens = Math.min(this.capacity, this.tokens + added); + this.lastRefill = now; + } + + /** + * Consume one token. Returns the wait time in ms before the token is available. + * 0 means the token was immediately available. + */ + consume() { + this._refill(); + if (this.tokens >= 1) { + this.tokens -= 1; + return 0; // no wait + } + // How long until 1 token is available? + const waitMs = Math.ceil((1 - this.tokens) / (this.refillRate / 1000)); + return waitMs; + } +} + +// ─── Request Queue ──────────────────────────────────────────────────────────── + +class RequestQueue { + constructor(concurrency = 5) { + this.concurrency = concurrency; + this.running = 0; + this.queue = []; + } + + enqueue(task) { + return new Promise((resolve, reject) => { + this.queue.push({ task, resolve, reject }); + this._drain(); + }); + } + + _drain() { + while (this.running < this.concurrency && this.queue.length > 0) { + const { task, resolve, reject } = this.queue.shift(); + this.running++; + Promise.resolve() + .then(() => task()) + .then(resolve, reject) + .finally(() => { + this.running--; + this._drain(); + }); + } + } +} + +// ─── Circuit Breaker ───────────────────────────────────────────────────────── + +const CircuitState = Object.freeze({ CLOSED: "CLOSED", OPEN: "OPEN", HALF_OPEN: "HALF_OPEN" }); + +class CircuitBreaker { + constructor({ failureThreshold = 5, recoveryTimeout = 30000 } = {}) { + this.failureThreshold = failureThreshold; + this.recoveryTimeout = recoveryTimeout; + this.state = CircuitState.CLOSED; + this.failures = 0; + this.openedAt = null; + } + + canRequest() { + if (this.state === CircuitState.CLOSED) return true; + if (this.state === CircuitState.OPEN) { + if (Date.now() - this.openedAt >= this.recoveryTimeout) { + this.state = CircuitState.HALF_OPEN; + return true; + } + return false; + } + // HALF_OPEN: allow one probe + return true; + } + + onSuccess() { + this.failures = 0; + this.state = CircuitState.CLOSED; + } + + onFailure() { + this.failures++; + if (this.failures >= this.failureThreshold) { + this.state = CircuitState.OPEN; + this.openedAt = Date.now(); + } + } + + get status() { + return this.state; + } +} + +// ─── Core: RateLimitedApiClient ─────────────────────────────────────────────── + +export class RateLimitedApiClient { + /** + * @param {object} config – override DEFAULT_CONFIG fields + */ + constructor(config = {}) { + this.config = { ...DEFAULT_CONFIG, ...config }; + + // One token bucket per "group" (e.g. endpoint category) + this._buckets = new Map(); + + // One circuit breaker per base URL / group + this._breakers = new Map(); + + // Shared request queue (controls global concurrency) + this._queue = new RequestQueue(this.config.concurrency ?? 10); + + // Metrics (lightweight, no deps) + this.metrics = { + totalRequests: 0, + successfulRequests: 0, + failedRequests: 0, + retriedRequests: 0, + rateLimitHits: 0, + }; + } + + // ── Internal helpers ─────────────────────────────────────────────────────── + + _getBucket(group) { + if (!this._buckets.has(group)) { + this._buckets.set( + group, + new TokenBucket( + this.config.rateLimitPerSecond, // burst = per-second limit + this.config.rateLimitPerSecond // refill = same + ) + ); + } + return this._buckets.get(group); + } + + _getBreaker(group) { + if (!this._breakers.has(group)) { + this._breakers.set(group, new CircuitBreaker()); + } + return this._breakers.get(group); + } + + /** + * Perform a fetch with a per-request timeout. + */ + async _fetchWithTimeout(url, options) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), this.config.timeout); + try { + return await fetch(url, { ...options, signal: controller.signal }); + } finally { + clearTimeout(timer); + } + } + + // ── Public API ───────────────────────────────────────────────────────────── + + /** + * Make a rate-limited, retried HTTP request. + * + * @param {string} url + * @param {RequestInit} options – standard fetch options + * @param {object} requestConfig + * @param {string} requestConfig.group – logical group for rate limiting ("quotes"|"fees"|"liquidity") + * @param {number} requestConfig.maxRetries – override global maxRetries + * @param {boolean} requestConfig.bypassQueue – skip queue (use sparingly) + * @returns {Promise} + */ + request(url, options = {}, requestConfig = {}) { + const task = () => this._execute(url, options, requestConfig); + if (requestConfig.bypassQueue) return task(); + return this._queue.enqueue(task); + } + + /** + * Convenience wrappers + */ + get(url, options, rc) { return this.request(url, { ...options, method: "GET" }, rc); } + post(url, body, options, rc) { + return this.request(url, { ...options, method: "POST", body: JSON.stringify(body), + headers: { "Content-Type": "application/json", ...(options?.headers) } }, rc); + } + + // ── Execution with retry ─────────────────────────────────────────────────── + + async _execute(url, options, requestConfig) { + const group = requestConfig.group ?? "default"; + const maxRetries = requestConfig.maxRetries ?? this.config.maxRetries; + const bucket = this._getBucket(group); + const breaker = this._getBreaker(group); + + this.metrics.totalRequests++; + + // Circuit breaker check + if (!breaker.canRequest()) { + const err = new Error(`Circuit OPEN for group "${group}". Requests suspended temporarily.`); + err.code = "CIRCUIT_OPEN"; + this.metrics.failedRequests++; + throw err; + } + + // Token bucket: throttle before even attempting + const wait = bucket.consume(); + if (wait > 0) { + this.metrics.rateLimitHits++; + await sleep(wait); + } + + let lastError; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + const response = await this._fetchWithTimeout(url, options); + + // Success path + if (response.ok) { + breaker.onSuccess(); + this.metrics.successfulRequests++; + return response; + } + + // Should we retry this status code? + if (!this.config.retryStatusCodes.includes(response.status)) { + // Non-retryable error (e.g. 400, 401, 403, 404) + breaker.onFailure(); + this.metrics.failedRequests++; + const err = new Error(`HTTP ${response.status}: ${response.statusText}`); + err.status = response.status; + err.response = response; + throw err; + } + + // Rate-limited: honour Retry-After if present + if (response.status === 429) { + this.metrics.rateLimitHits++; + const serverWait = parseRetryAfter(response); + const backoff = serverWait ?? computeBackoffDelay(attempt, this.config); + if (attempt < maxRetries) { + console.warn(`[BridgeWise] Rate limited (group=${group}). Waiting ${backoff}ms before retry ${attempt + 1}/${maxRetries}…`); + this.metrics.retriedRequests++; + await sleep(backoff); + continue; + } + } + + // 5xx: exponential backoff + const backoff = computeBackoffDelay(attempt, this.config); + if (attempt < maxRetries) { + console.warn(`[BridgeWise] HTTP ${response.status} (group=${group}). Retrying in ${backoff}ms (${attempt + 1}/${maxRetries})…`); + this.metrics.retriedRequests++; + await sleep(backoff); + continue; + } + + // Exhausted retries + lastError = new Error(`HTTP ${response.status} after ${maxRetries} retries`); + lastError.status = response.status; + lastError.response = response; + } catch (err) { + // Network / timeout error + if (err.code === "CIRCUIT_OPEN") throw err; + + lastError = err; + if (attempt < maxRetries) { + const isAbort = err.name === "AbortError"; + const backoff = computeBackoffDelay(attempt, this.config); + console.warn(`[BridgeWise] ${isAbort ? "Timeout" : "Network error"} (group=${group}). Retrying in ${backoff}ms (${attempt + 1}/${maxRetries})…`); + this.metrics.retriedRequests++; + await sleep(backoff); + } + } + } + + // All retries exhausted + breaker.onFailure(); + this.metrics.failedRequests++; + throw lastError ?? new Error("Request failed after maximum retries"); + } + + // ── Diagnostics ──────────────────────────────────────────────────────────── + + getCircuitStatus(group = "default") { + return this._breakers.get(group)?.status ?? CircuitState.CLOSED; + } + + getMetrics() { + return { ...this.metrics }; + } + + resetCircuit(group = "default") { + this._breakers.delete(group); + } +} + +// ─── BridgeWise Domain Clients ──────────────────────────────────────────────── + +const sharedClient = new RateLimitedApiClient({ + maxRetries: 3, + baseDelay: 800, + rateLimitPerSecond: 5, + rateLimitPerMinute: 100, + timeout: 8000, +}); + +/** + * Fetch bridge quotes with rate-limit handling. + * @param {object} params – { fromChain, toChain, token, amount } + */ +export async function fetchBridgeQuote(params) { + const url = `/api/bridge/quote?${new URLSearchParams(params)}`; + const response = await sharedClient.get(url, {}, { group: "quotes" }); + return response.json(); +} + +/** + * Fetch network fees. + * @param {string} chain + */ +export async function fetchNetworkFees(chain) { + const response = await sharedClient.get(`/api/fees/${chain}`, {}, { group: "fees" }); + return response.json(); +} + +/** + * Fetch liquidity & slippage data. + * @param {object} params + */ +export async function fetchLiquidityData(params) { + const url = `/api/liquidity?${new URLSearchParams(params)}`; + const response = await sharedClient.get(url, {}, { group: "liquidity" }); + return response.json(); +} + +export { sharedClient }; +export default RateLimitedApiClient; \ No newline at end of file diff --git a/packages/adapters/stellar/src/rateLimitedApi.test.js b/packages/adapters/stellar/src/rateLimitedApi.test.js new file mode 100644 index 0000000..4af6a97 --- /dev/null +++ b/packages/adapters/stellar/src/rateLimitedApi.test.js @@ -0,0 +1,236 @@ +/** + * BridgeWise — Tests for Rate-Limited API Handling Layer (Issue #64) + * Run with: node --experimental-vm-modules node_modules/.bin/jest rateLimitedApi.test.js + * Or: npx vitest rateLimitedApi.test.js + */ + +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import RateLimitedApiClient from "./rateLimitedApi.js"; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function makeFetchMock(responses) { + let call = 0; + return vi.fn(() => { + const r = responses[Math.min(call++, responses.length - 1)]; + if (r instanceof Error) return Promise.reject(r); + return Promise.resolve({ + ok: r.status >= 200 && r.status < 300, + status: r.status, + statusText: r.statusText ?? "", + headers: { get: (h) => r.headers?.[h] ?? null }, + json: () => Promise.resolve(r.body ?? {}), + }); + }); +} + +// Speed up tests by overriding sleep +vi.mock("./rateLimitedApi.js", async (importOriginal) => { + const mod = await importOriginal(); + return mod; // real module; we'll spy on setTimeout instead +}); + +beforeEach(() => { + vi.useFakeTimers(); +}); +afterEach(() => { + vi.useRealTimers(); + vi.restoreAllMocks(); +}); + +// ─── TokenBucket (internal — tested via integration) ───────────────────────── + +describe("RateLimitedApiClient — success path", () => { + it("returns response on first attempt when server returns 200", async () => { + const fetchMock = makeFetchMock([{ status: 200, body: { quote: 1.23 } }]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ rateLimitPerSecond: 100 }); + const promise = client.get("/api/quote", {}, { group: "quotes" }); + await vi.runAllTimersAsync(); + const res = await promise; + const data = await res.json(); + + expect(data.quote).toBe(1.23); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(client.getMetrics().successfulRequests).toBe(1); + expect(client.getMetrics().retriedRequests).toBe(0); + }); +}); + +describe("RateLimitedApiClient — retry on 5xx", () => { + it("retries up to maxRetries times then resolves on success", async () => { + const fetchMock = makeFetchMock([ + { status: 503, statusText: "Service Unavailable" }, + { status: 503, statusText: "Service Unavailable" }, + { status: 200, body: { fees: "0.001" } }, + ]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 3, baseDelay: 10, rateLimitPerSecond: 100 }); + const promise = client.get("/api/fees/eth", {}, { group: "fees" }); + await vi.runAllTimersAsync(); + const res = await promise; + + expect((await res.json()).fees).toBe("0.001"); + expect(fetchMock).toHaveBeenCalledTimes(3); // 2 failures + 1 success + expect(client.getMetrics().retriedRequests).toBe(2); + }); + + it("throws after exhausting all retries", async () => { + const fetchMock = makeFetchMock([ + { status: 500 }, { status: 500 }, { status: 500 }, { status: 500 }, + ]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 3, baseDelay: 10, rateLimitPerSecond: 100 }); + const promise = client.get("/api/fees/eth", {}, { group: "fees" }); + await vi.runAllTimersAsync(); + + await expect(promise).rejects.toThrow(); + expect(fetchMock).toHaveBeenCalledTimes(4); // 1 initial + 3 retries + expect(client.getMetrics().failedRequests).toBe(1); + }); +}); + +describe("RateLimitedApiClient — 429 rate limit handling", () => { + it("respects Retry-After header on 429", async () => { + const fetchMock = makeFetchMock([ + { status: 429, headers: { "Retry-After": "2" } }, + { status: 200, body: { liquidity: "high" } }, + ]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 3, baseDelay: 10, rateLimitPerSecond: 100 }); + const promise = client.get("/api/liquidity", {}, { group: "liquidity" }); + await vi.runAllTimersAsync(); + + const res = await promise; + expect((await res.json()).liquidity).toBe("high"); + expect(client.getMetrics().rateLimitHits).toBeGreaterThan(0); + }); + + it("falls back to exponential backoff when no Retry-After header", async () => { + const fetchMock = makeFetchMock([ + { status: 429 }, + { status: 200, body: {} }, + ]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 3, baseDelay: 10, rateLimitPerSecond: 100 }); + const promise = client.get("/api/liquidity", {}, { group: "liquidity" }); + await vi.runAllTimersAsync(); + + await expect(promise).resolves.toBeDefined(); + }); +}); + +describe("RateLimitedApiClient — non-retryable errors", () => { + it("does not retry on 400 Bad Request", async () => { + const fetchMock = makeFetchMock([{ status: 400, statusText: "Bad Request" }]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 3, rateLimitPerSecond: 100 }); + const promise = client.get("/api/quote", {}, { group: "quotes" }); + await vi.runAllTimersAsync(); + + await expect(promise).rejects.toMatchObject({ status: 400 }); + expect(fetchMock).toHaveBeenCalledTimes(1); // no retries + }); + + it("does not retry on 401 Unauthorized", async () => { + const fetchMock = makeFetchMock([{ status: 401 }]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 3, rateLimitPerSecond: 100 }); + const promise = client.get("/api/quote", {}, { group: "quotes" }); + await vi.runAllTimersAsync(); + + await expect(promise).rejects.toMatchObject({ status: 401 }); + expect(fetchMock).toHaveBeenCalledTimes(1); + }); +}); + +describe("RateLimitedApiClient — network errors & timeout", () => { + it("retries on network error and succeeds", async () => { + const fetchMock = vi.fn() + .mockRejectedValueOnce(new TypeError("Failed to fetch")) + .mockResolvedValueOnce({ ok: true, status: 200, headers: { get: () => null }, json: async () => ({}) }); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 3, baseDelay: 10, rateLimitPerSecond: 100 }); + const promise = client.get("/api/quote", {}, { group: "quotes" }); + await vi.runAllTimersAsync(); + + await expect(promise).resolves.toBeDefined(); + expect(fetchMock).toHaveBeenCalledTimes(2); + }); +}); + +describe("CircuitBreaker integration", () => { + it("opens circuit after repeated failures and rejects without fetching", async () => { + // Trigger 5 failures to open the breaker + const fetchMock = makeFetchMock(Array(10).fill({ status: 500 })); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ + maxRetries: 0, // no retries — each call counts as one failure + baseDelay: 1, + rateLimitPerSecond: 100, + }); + + // 5 failures should open the circuit + for (let i = 0; i < 5; i++) { + const p = client.get("/api/quote", {}, { group: "cb-test" }); + await vi.runAllTimersAsync(); + await p.catch(() => {}); + } + + expect(client.getCircuitStatus("cb-test")).toBe("OPEN"); + + // Next request should be rejected immediately without fetching + const callsBefore = fetchMock.mock.calls.length; + const blocked = client.get("/api/quote", {}, { group: "cb-test" }); + await vi.runAllTimersAsync(); + await expect(blocked).rejects.toMatchObject({ code: "CIRCUIT_OPEN" }); + expect(fetchMock.mock.calls.length).toBe(callsBefore); // no new fetch + }); + + it("resets circuit via resetCircuit()", async () => { + const fetchMock = makeFetchMock([{ status: 200 }]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 0, rateLimitPerSecond: 100 }); + client._getBreaker("grp").state = "OPEN"; + client._getBreaker("grp").openedAt = 0; // force expiry + + client.resetCircuit("grp"); + expect(client.getCircuitStatus("grp")).toBe("CLOSED"); + }); +}); + +describe("Metrics", () => { + it("tracks all metrics correctly over a mixed session", async () => { + const fetchMock = makeFetchMock([ + { status: 200 }, + { status: 500 }, + { status: 200 }, + ]); + globalThis.fetch = fetchMock; + + const client = new RateLimitedApiClient({ maxRetries: 1, baseDelay: 1, rateLimitPerSecond: 100 }); + + const p1 = client.get("/api/a", {}, { group: "g" }); + await vi.runAllTimersAsync(); + await p1; + + const p2 = client.get("/api/b", {}, { group: "g" }); + await vi.runAllTimersAsync(); + await p2; + + const m = client.getMetrics(); + expect(m.totalRequests).toBe(2); + expect(m.successfulRequests).toBe(2); + expect(m.retriedRequests).toBe(1); // second request retried once then succeeded + }); +}); \ No newline at end of file From c713fde760cdb2b0cd69381afd77826d42a958af Mon Sep 17 00:00:00 2001 From: phertyameen Date: Sat, 21 Feb 2026 16:09:21 +0100 Subject: [PATCH 2/2] fix: ci error --- libs/bridge-core/src/benchmark.ts | 166 +++-- package-lock.json | 845 ++++++++++++++++---------- package.json | 2 +- src/benchmark/benchmark.controller.ts | 14 +- src/benchmark/benchmark.module.ts | 2 +- src/benchmark/benchmark.service.ts | 98 +-- 6 files changed, 692 insertions(+), 435 deletions(-) diff --git a/libs/bridge-core/src/benchmark.ts b/libs/bridge-core/src/benchmark.ts index eb10a11..2801adb 100644 --- a/libs/bridge-core/src/benchmark.ts +++ b/libs/bridge-core/src/benchmark.ts @@ -3,14 +3,19 @@ * Collects, stores, and provides access to historical fee and slippage data */ -import type { BridgeRoute, ChainId, BridgeProvider, FeeSlippageBenchmark } from './types'; +import type { + BridgeRoute, + ChainId, + BridgeProvider, + FeeSlippageBenchmark, +} from './types'; export interface BenchmarkStorage { /** * Store a new benchmark record */ save(benchmark: FeeSlippageBenchmark): Promise; - + /** * Retrieve benchmarks by criteria */ @@ -19,9 +24,9 @@ export interface BenchmarkStorage { sourceChain: ChainId, destinationChain: ChainId, token: string, - limit?: number + limit?: number, ): Promise; - + /** * Get the most recent benchmark for a specific route */ @@ -29,9 +34,9 @@ export interface BenchmarkStorage { bridgeName: BridgeProvider, sourceChain: ChainId, destinationChain: ChainId, - token: string + token: string, ): Promise; - + /** * Get historical average for a specific route */ @@ -39,8 +44,12 @@ export interface BenchmarkStorage { bridgeName: BridgeProvider, sourceChain: ChainId, destinationChain: ChainId, - token: string - ): Promise<{ avgFee: number; avgSlippagePercent: number; sampleSize: number } | null>; + token: string, + ): Promise<{ + avgFee: number; + avgSlippagePercent: number; + sampleSize: number; + } | null>; } /** @@ -48,68 +57,88 @@ export interface BenchmarkStorage { */ export class InMemoryBenchmarkStorage implements BenchmarkStorage { private benchmarks: FeeSlippageBenchmark[] = []; - + async save(benchmark: FeeSlippageBenchmark): Promise { this.benchmarks.push(benchmark); // Keep only last 100 records per route to prevent memory bloat this.cleanupOldRecords(); } - + async getByCriteria( bridgeName: BridgeProvider, sourceChain: ChainId, destinationChain: ChainId, token: string, - limit: number = 10 + limit: number = 10, ): Promise { const filtered = this.benchmarks - .filter(b => - b.bridgeName === bridgeName && - b.sourceChain === sourceChain && - b.destinationChain === destinationChain && - b.token === token + .filter( + (b) => + b.bridgeName === bridgeName && + b.sourceChain === sourceChain && + b.destinationChain === destinationChain && + b.token === token, ) .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); - + return filtered.slice(0, limit); } - + async getLatest( bridgeName: BridgeProvider, sourceChain: ChainId, destinationChain: ChainId, - token: string + token: string, ): Promise { - const records = await this.getByCriteria(bridgeName, sourceChain, destinationChain, token, 1); + const records = await this.getByCriteria( + bridgeName, + sourceChain, + destinationChain, + token, + 1, + ); return records.length > 0 ? records[0] : null; } - + async getAverage( bridgeName: BridgeProvider, sourceChain: ChainId, destinationChain: ChainId, - token: string - ): Promise<{ avgFee: number; avgSlippagePercent: number; sampleSize: number } | null> { - const records = await this.getByCriteria(bridgeName, sourceChain, destinationChain, token, 100); - + token: string, + ): Promise<{ + avgFee: number; + avgSlippagePercent: number; + sampleSize: number; + } | null> { + const records = await this.getByCriteria( + bridgeName, + sourceChain, + destinationChain, + token, + 100, + ); + if (records.length === 0) { return null; } - + const sumFee = records.reduce((sum, record) => sum + record.avgFee, 0); - const sumSlippage = records.reduce((sum, record) => sum + record.avgSlippagePercent, 0); - + const sumSlippage = records.reduce( + (sum, record) => sum + record.avgSlippagePercent, + 0, + ); + return { avgFee: sumFee / records.length, avgSlippagePercent: sumSlippage / records.length, - sampleSize: records.length + sampleSize: records.length, }; } - + private cleanupOldRecords(): void { // Group by route criteria const grouped = new Map(); - + for (const benchmark of this.benchmarks) { const key = `${benchmark.bridgeName}-${benchmark.sourceChain}-${benchmark.destinationChain}-${benchmark.token}`; if (!grouped.has(key)) { @@ -117,14 +146,16 @@ export class InMemoryBenchmarkStorage implements BenchmarkStorage { } grouped.get(key)!.push(benchmark); } - + // Keep only last 100 records per group const kept: FeeSlippageBenchmark[] = []; for (const records of grouped.values()) { - const sorted = records.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); + const sorted = records.sort( + (a, b) => b.timestamp.getTime() - a.timestamp.getTime(), + ); kept.push(...sorted.slice(0, 100)); } - + this.benchmarks = kept; } } @@ -134,11 +165,11 @@ export class InMemoryBenchmarkStorage implements BenchmarkStorage { */ export class BenchmarkService { private storage: BenchmarkStorage; - + constructor(storage: BenchmarkStorage = new InMemoryBenchmarkStorage()) { this.storage = storage; } - + /** * Process a completed bridge route to extract benchmark data */ @@ -146,15 +177,16 @@ export class BenchmarkService { try { // Extract fee percentage from the route const feePercentage = route.feePercentage; - + // Calculate slippage percentage const inputNum = parseFloat(inputAmount); const outputNum = parseFloat(route.outputAmount); - const expectedOutput = inputNum - (inputNum * feePercentage / 100); - const slippagePercent = expectedOutput > 0 - ? Math.abs(((expectedOutput - outputNum) / expectedOutput) * 100) - : 0; - + const expectedOutput = inputNum - (inputNum * feePercentage) / 100; + const slippagePercent = + expectedOutput > 0 + ? Math.abs(((expectedOutput - outputNum) / expectedOutput) * 100) + : 0; + // Create benchmark record const benchmark: FeeSlippageBenchmark = { bridgeName: route.provider, @@ -165,14 +197,14 @@ export class BenchmarkService { avgSlippagePercent: slippagePercent, timestamp: new Date(), }; - + // Save to storage await this.storage.save(benchmark); } catch (error) { console.error('Error processing route for benchmark:', error); } } - + /** * Get benchmark data for a specific route */ @@ -180,11 +212,16 @@ export class BenchmarkService { bridgeName: BridgeProvider, sourceChain: ChainId, destinationChain: ChainId, - token: string + token: string, ): Promise { - return await this.storage.getByCriteria(bridgeName, sourceChain, destinationChain, token); + return await this.storage.getByCriteria( + bridgeName, + sourceChain, + destinationChain, + token, + ); } - + /** * Get the latest benchmark for a specific route */ @@ -192,11 +229,16 @@ export class BenchmarkService { bridgeName: BridgeProvider, sourceChain: ChainId, destinationChain: ChainId, - token: string + token: string, ): Promise { - return await this.storage.getLatest(bridgeName, sourceChain, destinationChain, token); + return await this.storage.getLatest( + bridgeName, + sourceChain, + destinationChain, + token, + ); } - + /** * Get historical averages for a specific route */ @@ -204,17 +246,29 @@ export class BenchmarkService { bridgeName: BridgeProvider, sourceChain: ChainId, destinationChain: ChainId, - token: string - ): Promise<{ avgFee: number; avgSlippagePercent: number; sampleSize: number } | null> { - return await this.storage.getAverage(bridgeName, sourceChain, destinationChain, token); + token: string, + ): Promise<{ + avgFee: number; + avgSlippagePercent: number; + sampleSize: number; + } | null> { + return await this.storage.getAverage( + bridgeName, + sourceChain, + destinationChain, + token, + ); } - + /** * Normalize benchmark data across different chains and tokens */ - normalizeBenchmark(benchmark: FeeSlippageBenchmark, baseToken: string = 'USDC'): number { + normalizeBenchmark( + benchmark: FeeSlippageBenchmark, + baseToken: string = 'USDC', + ): number { // In a real implementation, this would convert different tokens to a common base // For now, we'll just return the fee percentage as-is return benchmark.avgFee; } -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index 26c9a81..caf330c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@nestjs/event-emitter": "^3.0.1", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^11.0.1", - "@nestjs/swagger": "^7.1.17", + "@nestjs/swagger": "^11.2.6", "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^11.0.0", "@stellar/freighter-api": "^6.0.1", @@ -210,9 +210,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -225,9 +225,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -235,21 +235,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -276,14 +276,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -416,13 +416,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -686,18 +686,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -705,9 +705,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -750,7 +750,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -763,7 +763,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -912,9 +912,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", "dev": true, "license": "MIT", "engines": { @@ -1350,29 +1350,6 @@ } } }, - "node_modules/@isaacs/balanced-match": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", - "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@isaacs/balanced-match": "^4.0.1" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1820,6 +1797,7 @@ "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": { @@ -2025,7 +2003,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2046,7 +2024,7 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -2070,9 +2048,9 @@ } }, "node_modules/@microsoft/tsdoc": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz", - "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { @@ -2144,10 +2122,181 @@ } } }, + "node_modules/@nestjs/cli/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nestjs/cli/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/@nestjs/cli/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nestjs/cli/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nestjs/cli/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/cli/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/cli/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@nestjs/cli/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@nestjs/cli/node_modules/webpack": { + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, "node_modules/@nestjs/common": { - "version": "11.1.12", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.12.tgz", - "integrity": "sha512-v6U3O01YohHO+IE3EIFXuRuu3VJILWzyMmSYZXpyBbnp0hk0mFyHxK2w3dF4I5WnbwiRbWlEXdeXFvPQ7qaZzw==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.14.tgz", + "integrity": "sha512-IN/tlqd7Nl9gl6f0jsWEuOrQDaCI9vHzxv0fisHysfBQzfQIkqlv5A7w4Qge02BUQyczXT9HHPgHtWHCxhjRng==", "license": "MIT", "dependencies": { "file-type": "21.3.0", @@ -2176,14 +2325,14 @@ } }, "node_modules/@nestjs/config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", - "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.3.tgz", + "integrity": "sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==", "license": "MIT", "dependencies": { - "dotenv": "16.4.7", - "dotenv-expand": "12.0.1", - "lodash": "4.17.21" + "dotenv": "17.2.3", + "dotenv-expand": "12.0.3", + "lodash": "4.17.23" }, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", @@ -2191,9 +2340,9 @@ } }, "node_modules/@nestjs/core": { - "version": "11.1.12", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.12.tgz", - "integrity": "sha512-97DzTYMf5RtGAVvX1cjwpKRiCUpkeQ9CCzSAenqkAhOmNVVFaApbhuw+xrDt13rsCa2hHVOYPrV4dBgOYMJjsA==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.14.tgz", + "integrity": "sha512-7OXPPMoDr6z+5NkoQKu4hOhfjz/YYqM3bNilPqv1WVFWrzSmuNXxvhbX69YMmNmRYascPXiwESqf5jJdjKXEww==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -2265,12 +2414,12 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.1.12", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.12.tgz", - "integrity": "sha512-GYK/vHI0SGz5m8mxr7v3Urx8b9t78Cf/dj5aJMZlGd9/1D9OI1hAl00BaphjEXINUJ/BQLxIlF2zUjrYsd6enQ==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.14.tgz", + "integrity": "sha512-Fs+/j+mBSBSXErOQJ/YdUn/HqJGSJ4pGfiJyYOyz04l42uNVnqEakvu1kXLbxMabR6vd6/h9d6Bi4tso9p7o4Q==", "license": "MIT", "dependencies": { - "cors": "2.8.5", + "cors": "2.8.6", "express": "5.2.1", "multer": "2.0.2", "path-to-regexp": "8.3.0", @@ -2384,22 +2533,22 @@ } }, "node_modules/@nestjs/swagger": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.4.2.tgz", - "integrity": "sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.6.tgz", + "integrity": "sha512-oiXOxMQqDFyv1AKAqFzSo6JPvMEs4uA36Eyz/s2aloZLxUjcLfUMELSLSNQunr61xCPTpwEOShfmO7NIufKXdA==", "license": "MIT", "dependencies": { - "@microsoft/tsdoc": "^0.15.0", - "@nestjs/mapped-types": "2.0.5", - "js-yaml": "4.1.0", - "lodash": "4.17.21", - "path-to-regexp": "3.3.0", - "swagger-ui-dist": "5.17.14" + "@microsoft/tsdoc": "0.16.0", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.1", + "lodash": "4.17.23", + "path-to-regexp": "8.3.0", + "swagger-ui-dist": "5.31.0" }, "peerDependencies": { - "@fastify/static": "^6.0.0 || ^7.0.0", - "@nestjs/common": "^9.0.0 || ^10.0.0", - "@nestjs/core": "^9.0.0 || ^10.0.0", + "@fastify/static": "^8.0.0 || ^9.0.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", "class-transformer": "*", "class-validator": "*", "reflect-metadata": "^0.1.12 || ^0.2.0" @@ -2416,48 +2565,10 @@ } } }, - "node_modules/@nestjs/swagger/node_modules/@nestjs/mapped-types": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz", - "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "class-transformer": "^0.4.0 || ^0.5.0", - "class-validator": "^0.13.0 || ^0.14.0", - "reflect-metadata": "^0.1.12 || ^0.2.0" - }, - "peerDependenciesMeta": { - "class-transformer": { - "optional": true - }, - "class-validator": { - "optional": true - } - } - }, - "node_modules/@nestjs/swagger/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@nestjs/swagger/node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", - "license": "MIT" - }, "node_modules/@nestjs/testing": { - "version": "11.1.12", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.12.tgz", - "integrity": "sha512-W0M/i5nb9qRQpTQfJm+1mGT/+y4YezwwdcD7mxFG8JEZ5fz/ZEAk1Ayri2VBJKJUdo20B1ggnvqew4dlTMrSNg==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.14.tgz", + "integrity": "sha512-cQxX0ronsTbpfHz8/LYOVWXxoTxv6VoxrnuZoQaVX7QV2PSMqxWE7/9jSQR0GcqAFUEmFP34c6EJqfkjfX/k4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2568,6 +2679,13 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sinclair/typebox": { "version": "0.34.48", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", @@ -2664,28 +2782,28 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { @@ -2886,10 +3004,10 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", - "dev": true, + "version": "22.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3005,17 +3123,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.0.tgz", + "integrity": "sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/type-utils": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -3028,8 +3146,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", - "eslint": "^8.57.0 || ^9.0.0", + "@typescript-eslint/parser": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -3044,16 +3162,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.0.tgz", + "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", "debug": "^4.4.3" }, "engines": { @@ -3064,19 +3182,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.0.tgz", + "integrity": "sha512-M3rnyL1vIQOMeWxTWIW096/TtVP+8W3p/XnaFflhmcFp+U4zlxUxWj4XwNs6HbDeTtN4yun0GNTTDBw/SvufKg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", + "@typescript-eslint/tsconfig-utils": "^8.56.0", + "@typescript-eslint/types": "^8.56.0", "debug": "^4.4.3" }, "engines": { @@ -3091,14 +3209,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.0.tgz", + "integrity": "sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3109,9 +3227,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.0.tgz", + "integrity": "sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==", "dev": true, "license": "MIT", "engines": { @@ -3126,15 +3244,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.0.tgz", + "integrity": "sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -3146,14 +3264,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.0.tgz", + "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==", "dev": true, "license": "MIT", "engines": { @@ -3165,16 +3283,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.0.tgz", + "integrity": "sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", + "@typescript-eslint/project-service": "8.56.0", + "@typescript-eslint/tsconfig-utils": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -3219,9 +3337,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -3232,16 +3350,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.0.tgz", + "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3251,19 +3369,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.0.tgz", + "integrity": "sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.54.0", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.56.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3274,13 +3392,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -3751,10 +3869,10 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3787,10 +3905,10 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "devOptional": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -3800,9 +3918,9 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { @@ -3835,9 +3953,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -3973,7 +4091,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/argparse": { @@ -4018,13 +4136,13 @@ } }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -4134,9 +4252,9 @@ "license": "MIT" }, "node_modules/bare-addon-resolve": { - "version": "1.9.6", - "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.9.6.tgz", - "integrity": "sha512-hvOQY1zDK6u0rSr27T6QlULoVLwi8J2k8HHHJlxSfT7XQdQ/7bsS+AnjYkHtu/TkL+gm3aMXAKucJkJAbrDG/g==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.10.0.tgz", + "integrity": "sha512-sSd0jieRJlDaODOzj0oe0RjFVC1QI0ZIjGIdPkbrTXsdVVtENg14c+lHHAhHwmWCZ2nQlMhy8jA3Y5LYPc/isA==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -4207,13 +4325,16 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/bignumber.js": { @@ -4485,9 +4606,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", "dev": true, "funding": [ { @@ -4874,9 +4995,9 @@ "license": "MIT" }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -4884,6 +5005,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cosmiconfig": { @@ -4917,7 +5042,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -5068,16 +5193,16 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5087,9 +5212,9 @@ } }, "node_modules/dotenv-expand": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", - "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", "license": "BSD-2-Clause", "dependencies": { "dotenv": "^16.4.5" @@ -5101,6 +5226,18 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-expand/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5128,9 +5265,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.282", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.282.tgz", - "integrity": "sha512-FCPkJtpst28UmFzd903iU7PdeVTfY0KAeJy+Lk0GLZRwgwYHn/irRcaCbQQOmr5Vytc/7rcavsYLvTM8RiHYhQ==", + "version": "1.5.302", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", + "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true, "license": "ISC" }, @@ -5163,14 +5300,14 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -5267,9 +5404,9 @@ } }, "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", "dependencies": { @@ -5279,7 +5416,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", + "@eslint/js": "9.39.3", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -6156,17 +6293,40 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/glob/node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/glob/node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/brace-expansion": "^5.0.0" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -6874,6 +7034,7 @@ "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": { @@ -7252,6 +7413,7 @@ "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": { @@ -7343,9 +7505,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -7468,7 +7630,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -7586,9 +7747,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.35", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.35.tgz", - "integrity": "sha512-T/Cz6iLcsZdb5jDncDcUNhSAJ0VlSC9TnsqtBNdpkaAmy24/R1RhErtNWVWBrcUZKs9hSgaVsBkc7HxYnazIfw==", + "version": "1.12.37", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.37.tgz", + "integrity": "sha512-rDU6bkpuMs8YRt/UpkuYEAsYSoNuDEbrE41I3KNvmXREGH6DGBJ8Wbak4by29wNOQ27zk4g4HL82zf0OGhwRuw==", "license": "MIT" }, "node_modules/lines-and-columns": { @@ -7648,9 +7809,9 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.memoize": { @@ -7724,7 +7885,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/makeerror": { @@ -7895,10 +8056,10 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -8313,9 +8474,9 @@ } }, "node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "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": "BlueOak-1.0.0", "dependencies": { @@ -8323,16 +8484,16 @@ "minipass": "^7.1.2" }, "engines": { - "node": "20 || >=22" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", - "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -8360,12 +8521,12 @@ } }, "node_modules/pg": { - "version": "8.17.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz", - "integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.10.1", + "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", @@ -8394,9 +8555,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.1.tgz", - "integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", "license": "MIT" }, "node_modules/pg-int8": { @@ -8719,9 +8880,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -9524,10 +9685,13 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.17.14", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz", - "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==", - "license": "Apache-2.0" + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } }, "node_modules/swagger-ui-express": { "version": "5.0.1", @@ -9639,9 +9803,9 @@ } }, "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -9770,7 +9934,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "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": { @@ -9952,9 +10116,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { @@ -10002,7 +10166,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -10292,6 +10456,7 @@ "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", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -10362,7 +10527,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10373,16 +10538,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.0.tgz", + "integrity": "sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" + "@typescript-eslint/eslint-plugin": "8.56.0", + "@typescript-eslint/parser": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10392,7 +10557,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, @@ -10438,7 +10603,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/universalify": { @@ -10565,7 +10730,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -10636,11 +10801,12 @@ } }, "node_modules/webpack": { - "version": "5.104.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", - "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", + "version": "5.105.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.2.tgz", + "integrity": "sha512-dRXm0a2qcHPUBEzVk8uph0xWSjV/xZxenQQbLwnwP7caQCYpqG1qddwlyEkIDkYn0K8tvmcrZ+bOrzoQ3HxCDw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -10652,7 +10818,7 @@ "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.4", + "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -10665,7 +10831,7 @@ "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.16", - "watchpack": "^2.4.4", + "watchpack": "^2.5.1", "webpack-sources": "^3.3.3" }, "bin": { @@ -10695,9 +10861,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", "engines": { @@ -10705,11 +10871,12 @@ } }, "node_modules/webpack/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -10727,6 +10894,7 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -10745,6 +10913,7 @@ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -10758,6 +10927,7 @@ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -10772,6 +10942,7 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=4.0" } @@ -10781,7 +10952,8 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/webpack/node_modules/mime-db": { "version": "1.52.0", @@ -10789,6 +10961,7 @@ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -10799,6 +10972,7 @@ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -10812,6 +10986,7 @@ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -10988,7 +11163,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" diff --git a/package.json b/package.json index 1e0458f..d1dce8f 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@nestjs/event-emitter": "^3.0.1", "@nestjs/mapped-types": "*", "@nestjs/platform-express": "^11.0.1", - "@nestjs/swagger": "^7.1.17", + "@nestjs/swagger": "^11.2.6", "@nestjs/throttler": "^6.5.0", "@nestjs/typeorm": "^11.0.0", "@stellar/freighter-api": "^6.0.1", diff --git a/src/benchmark/benchmark.controller.ts b/src/benchmark/benchmark.controller.ts index 4c9a9e5..ec49a5c 100644 --- a/src/benchmark/benchmark.controller.ts +++ b/src/benchmark/benchmark.controller.ts @@ -17,7 +17,7 @@ export class BenchmarkController { sourceChain?: string, destinationChain?: string, token?: string, - limit: number = 50 + limit: number = 50, ): Promise { const query = { bridgeName, sourceChain, destinationChain, token }; return await this.benchmarkService.getBenchmarks(query, limit); @@ -27,7 +27,7 @@ export class BenchmarkController { bridgeName: string, sourceChain: string, destinationChain: string, - token: string + token: string, ): Promise { const query = { bridgeName, sourceChain, destinationChain, token }; return await this.benchmarkService.getLatestBenchmark(query); @@ -37,9 +37,13 @@ export class BenchmarkController { bridgeName: string, sourceChain: string, destinationChain: string, - token: string - ): Promise<{ avgFee: number; avgSlippagePercent: number; sampleSize: number } | null> { + token: string, + ): Promise<{ + avgFee: number; + avgSlippagePercent: number; + sampleSize: number; + } | null> { const query = { bridgeName, sourceChain, destinationChain, token }; return await this.benchmarkService.getAverageBenchmark(query); } -} \ No newline at end of file +} diff --git a/src/benchmark/benchmark.module.ts b/src/benchmark/benchmark.module.ts index e845c57..f2a76b3 100644 --- a/src/benchmark/benchmark.module.ts +++ b/src/benchmark/benchmark.module.ts @@ -14,4 +14,4 @@ export class BenchmarkModule { } } -export { BenchmarkService, BenchmarkController }; \ No newline at end of file +export { BenchmarkService, BenchmarkController }; diff --git a/src/benchmark/benchmark.service.ts b/src/benchmark/benchmark.service.ts index 63b9c02..215f32c 100644 --- a/src/benchmark/benchmark.service.ts +++ b/src/benchmark/benchmark.service.ts @@ -25,7 +25,7 @@ interface BenchmarkQuery { export class BenchmarkService { private benchmarks: FeeSlippageBenchmark[] = []; - + /** * Save benchmark data to storage */ @@ -38,25 +38,38 @@ export class BenchmarkService { /** * Get benchmarks by query criteria */ - async getBenchmarks(query: BenchmarkQuery, limit: number = 50): Promise { - let results = this.benchmarks.filter(benchmark => { - if (query.bridgeName && benchmark.bridgeName !== query.bridgeName) return false; - if (query.sourceChain && benchmark.sourceChain !== query.sourceChain) return false; - if (query.destinationChain && benchmark.destinationChain !== query.destinationChain) return false; + async getBenchmarks( + query: BenchmarkQuery, + limit: number = 50, + ): Promise { + let results = this.benchmarks.filter((benchmark) => { + if (query.bridgeName && benchmark.bridgeName !== query.bridgeName) + return false; + if (query.sourceChain && benchmark.sourceChain !== query.sourceChain) + return false; + if ( + query.destinationChain && + benchmark.destinationChain !== query.destinationChain + ) + return false; if (query.token && benchmark.token !== query.token) return false; return true; }); - + // Sort by timestamp descending and limit results - results = results.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()).slice(0, limit); - + results = results + .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) + .slice(0, limit); + return results; } /** * Get the latest benchmark for a specific route */ - async getLatestBenchmark(query: BenchmarkQuery): Promise { + async getLatestBenchmark( + query: BenchmarkQuery, + ): Promise { const benchmarks = await this.getBenchmarks(query, 1); return benchmarks.length > 0 ? benchmarks[0] : null; } @@ -70,18 +83,21 @@ export class BenchmarkService { sampleSize: number; } | null> { const benchmarks = await this.getBenchmarks(query, 1000); // Get all available records - + if (benchmarks.length === 0) { return null; } - + const sumFee = benchmarks.reduce((sum, b) => sum + b.avgFee, 0); - const sumSlippage = benchmarks.reduce((sum, b) => sum + b.avgSlippagePercent, 0); - + const sumSlippage = benchmarks.reduce( + (sum, b) => sum + b.avgSlippagePercent, + 0, + ); + return { avgFee: sumFee / benchmarks.length, avgSlippagePercent: sumSlippage / benchmarks.length, - sampleSize: benchmarks.length + sampleSize: benchmarks.length, }; } @@ -90,23 +106,25 @@ export class BenchmarkService { */ async processTransaction(transactionData: any): Promise { // Extract fee and slippage information from the transaction - const { - provider, - sourceChain, - destinationChain, - token, - feePercentage, - slippage, + const { + provider, + sourceChain, + destinationChain, + token, + feePercentage, + slippage, inputAmount, - outputAmount + outputAmount, } = transactionData; // Calculate actual slippage percentage - const slippagePercent = slippage || this.calculateSlippage( - parseFloat(inputAmount), - parseFloat(outputAmount), - feePercentage - ); + const slippagePercent = + slippage || + this.calculateSlippage( + parseFloat(inputAmount), + parseFloat(outputAmount), + feePercentage, + ); // Create benchmark record const benchmark: FeeSlippageBenchmark = { @@ -117,7 +135,7 @@ export class BenchmarkService { avgFee: feePercentage, avgSlippagePercent: slippagePercent, timestamp: new Date(), - sampleSize: 1 + sampleSize: 1, }; await this.saveBenchmark(benchmark); @@ -126,15 +144,19 @@ export class BenchmarkService { /** * Calculate slippage percentage based on input/output amounts and fees */ - private calculateSlippage(inputAmount: number, outputAmount: number, feePercentage: number): number { + private calculateSlippage( + inputAmount: number, + outputAmount: number, + feePercentage: number, + ): number { // Calculate expected output after fees const expectedOutput = inputAmount * (1 - feePercentage / 100); - + // Calculate the actual slippage if (expectedOutput > 0) { return Math.abs(((expectedOutput - outputAmount) / expectedOutput) * 100); } - + return 0; } @@ -144,7 +166,7 @@ export class BenchmarkService { private cleanupOldRecords(): void { // Group by route criteria const grouped = new Map(); - + for (const benchmark of this.benchmarks) { const key = `${benchmark.bridgeName}-${benchmark.sourceChain}-${benchmark.destinationChain}-${benchmark.token}`; if (!grouped.has(key)) { @@ -152,14 +174,16 @@ export class BenchmarkService { } grouped.get(key)!.push(benchmark); } - + // Keep only last 100 records per group const kept: FeeSlippageBenchmark[] = []; for (const records of grouped.values()) { - const sorted = records.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); + const sorted = records.sort( + (a, b) => b.timestamp.getTime() - a.timestamp.getTime(), + ); kept.push(...sorted.slice(0, 100)); } - + this.benchmarks = kept; } -} \ No newline at end of file +}