From 289598c65913dda83d72de45867ba0b3758c5aa1 Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 20 Feb 2026 12:36:15 +0100 Subject: [PATCH 1/2] quote refresh component created --- apps/web/components/BridgeCompare.tsx | 140 ++++++++++ apps/web/components/RefreshIndicator.tsx | 73 +++++ .../{ui-lib => }/TransactionHeartbeat.tsx | 2 +- .../ui-lib/hooks/useBridgeQuotes.ts | 206 ++++++++++++++ apps/web/components/ui-lib/index.ts | 2 +- apps/web/services/QuoteRefreshEngine.ts | 262 ++++++++++++++++++ apps/web/types/quote.types.ts | 55 ++++ tsconfig.json | 2 +- 8 files changed, 739 insertions(+), 3 deletions(-) create mode 100644 apps/web/components/BridgeCompare.tsx create mode 100644 apps/web/components/RefreshIndicator.tsx rename apps/web/components/{ui-lib => }/TransactionHeartbeat.tsx (97%) create mode 100644 apps/web/components/ui-lib/hooks/useBridgeQuotes.ts create mode 100644 apps/web/services/QuoteRefreshEngine.ts create mode 100644 apps/web/types/quote.types.ts diff --git a/apps/web/components/BridgeCompare.tsx b/apps/web/components/BridgeCompare.tsx new file mode 100644 index 0000000..510f298 --- /dev/null +++ b/apps/web/components/BridgeCompare.tsx @@ -0,0 +1,140 @@ +// packages/ui/src/components/BridgeCompare.tsx + +import React, { useState, useEffect } from 'react'; +import { useBridgeQuotes, BridgeQuoteParams } from '@bridgewise/react'; +import { RefreshIndicator } from './RefreshIndicator'; +import { QuoteCard } from './QuoteCard'; +import { SlippageWarning } from './SlippageWarning'; + +interface BridgeCompareProps { + initialParams: BridgeQuoteParams; + onQuoteSelect?: (quoteId: string) => void; + refreshInterval?: number; + autoRefresh?: boolean; +} + +export const BridgeCompare: React.FC = ({ + initialParams, + onQuoteSelect, + refreshInterval = 15000, + autoRefresh = true +}) => { + const [selectedQuoteId, setSelectedQuoteId] = useState(null); + const [showRefreshIndicator, setShowRefreshIndicator] = useState(false); + + const { + quotes, + isLoading, + error, + lastRefreshed, + isRefreshing, + refresh, + updateParams, + retryCount + } = useBridgeQuotes({ + initialParams, + intervalMs: refreshInterval, + autoRefresh, + onRefreshStart: () => setShowRefreshIndicator(true), + onRefreshEnd: () => { + setTimeout(() => setShowRefreshIndicator(false), 1000); + } + }); + + // Handle quote selection + const handleQuoteSelect = (quoteId: string) => { + setSelectedQuoteId(quoteId); + onQuoteSelect?.(quoteId); + }; + + // Format last refreshed time + const getLastRefreshedText = () => { + if (!lastRefreshed) return 'Never'; + + const seconds = Math.floor((Date.now() - lastRefreshed.getTime()) / 1000); + + if (seconds < 60) return `${seconds}s ago`; + if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; + return lastRefreshed.toLocaleTimeString(); + }; + + return ( +
+ {/* Header with refresh controls */} +
+

Bridge Routes

+ +
+ + +
+ + Updated: {getLastRefreshedText()} + + {retryCount > 0 && ( + + Retry {retryCount}/{3} + + )} +
+
+
+ + {/* Error state */} + {error && ( +
+

Failed to fetch quotes: {error.message}

+ +
+ )} + + {/* Loading skeleton */} + {isLoading && quotes.length === 0 && ( +
+ {[1, 2, 3].map((i) => ( +
+ ))} +
+ )} + + {/* Quotes grid */} + {quotes.length > 0 && ( +
+ {quotes.map((quote) => ( + handleQuoteSelect(quote.id)} + isRefreshing={isRefreshing && showRefreshIndicator} + /> + ))} +
+ )} + + {/* Slippage warning for outdated quotes */} + {lastRefreshed && ( + + )} + + {/* Empty state */} + {!isLoading && quotes.length === 0 && !error && ( +
+

No bridge routes found for the selected parameters

+
+ )} +
+ ); +}; \ No newline at end of file diff --git a/apps/web/components/RefreshIndicator.tsx b/apps/web/components/RefreshIndicator.tsx new file mode 100644 index 0000000..1c6fcbb --- /dev/null +++ b/apps/web/components/RefreshIndicator.tsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from 'react'; + +interface RefreshIndicatorProps { + isRefreshing: boolean; + lastRefreshed: Date | null; + onClick: () => void; + showAnimation?: boolean; +} + +export const RefreshIndicator: React.FC = ({ + isRefreshing, + lastRefreshed, + onClick, + showAnimation = false +}) => { + const [animate, setAnimate] = useState(false); + + useEffect(() => { + if (showAnimation) { + setAnimate(true); + const timer = setTimeout(() => setAnimate(false), 1000); + return () => clearTimeout(timer); + } + }, [showAnimation]); + + const getTimeSinceLastRefresh = () => { + if (!lastRefreshed) return 'Never'; + + const seconds = Math.floor((Date.now() - lastRefreshed.getTime()) / 1000); + + if (seconds < 60) return `${seconds}s`; + if (seconds < 3600) return `${Math.floor(seconds / 60)}m`; + return `${Math.floor(seconds / 3600)}h`; + }; + + return ( + + ); +}; \ No newline at end of file diff --git a/apps/web/components/ui-lib/TransactionHeartbeat.tsx b/apps/web/components/TransactionHeartbeat.tsx similarity index 97% rename from apps/web/components/ui-lib/TransactionHeartbeat.tsx rename to apps/web/components/TransactionHeartbeat.tsx index 2dcf806..11c76f4 100644 --- a/apps/web/components/ui-lib/TransactionHeartbeat.tsx +++ b/apps/web/components/TransactionHeartbeat.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { useTransactionPersistence } from './hooks/useTransactionPersistence'; +import { useTransactionPersistence } from './ui-lib/hooks/useTransactionPersistence'; export const TransactionHeartbeat = () => { const { state, clearState } = useTransactionPersistence(); diff --git a/apps/web/components/ui-lib/hooks/useBridgeQuotes.ts b/apps/web/components/ui-lib/hooks/useBridgeQuotes.ts new file mode 100644 index 0000000..3f40138 --- /dev/null +++ b/apps/web/components/ui-lib/hooks/useBridgeQuotes.ts @@ -0,0 +1,206 @@ +// packages/react/src/hooks/useBridgeQuotes.ts + +import { useState, useEffect, useCallback, useRef } from 'react'; +import { QuoteRefreshEngine } from '@bridgewise/core'; +import { NormalizedQuote, QuoteRefreshConfig, RefreshState } from '@bridgewise/core/types'; + +export interface UseBridgeQuotesOptions extends QuoteRefreshConfig { + initialParams?: BridgeQuoteParams; + debounceMs?: number; +} + +export interface BridgeQuoteParams { + amount: string; + sourceChain: string; + destinationChain: string; + sourceToken: string; + destinationToken: string; + userAddress?: string; + slippageTolerance?: number; +} + +export interface UseBridgeQuotesReturn { + quotes: NormalizedQuote[]; + isLoading: boolean; + error: Error | null; + lastRefreshed: Date | null; + isRefreshing: boolean; + refresh: () => Promise; + updateParams: (params: Partial) => void; + retryCount: number; +} + +export function useBridgeQuotes( + options: UseBridgeQuotesOptions = {} +): UseBridgeQuotesReturn { + const { + initialParams, + intervalMs = 15000, + autoRefresh = true, + maxRetries = 3, + retryDelayMs = 1000, + debounceMs = 300, + ...callbacks + } = options; + + const [params, setParams] = useState(initialParams); + const [quotes, setQuotes] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const [lastRefreshed, setLastRefreshed] = useState(null); + const [isRefreshing, setIsRefreshing] = useState(false); + const [retryCount, setRetryCount] = useState(0); + + const engineRef = useRef(null); + const debounceTimerRef = useRef(); + const paramsRef = useRef(params); + + // Update params ref on change + useEffect(() => { + paramsRef.current = params; + }, [params]); + + // Initialize refresh engine + useEffect(() => { + const fetchQuotes = async (fetchParams: BridgeQuoteParams, options?: { signal?: AbortSignal }) => { + // Implement actual quote fetching logic here + const response = await fetch('/api/quotes', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(fetchParams), + signal: options?.signal + }); + + if (!response.ok) { + throw new Error('Failed to fetch quotes'); + } + + return response.json(); + }; + + engineRef.current = new QuoteRefreshEngine(fetchQuotes, { + intervalMs, + autoRefresh, + maxRetries, + retryDelayMs, + onRefresh: (newQuotes) => { + setQuotes(newQuotes); + setLastRefreshed(new Date()); + callbacks.onRefresh?.(newQuotes); + }, + onError: (err) => { + setError(err); + callbacks.onError?.(err); + }, + onRefreshStart: () => { + setIsRefreshing(true); + callbacks.onRefreshStart?.(); + }, + onRefreshEnd: () => { + setIsRefreshing(false); + callbacks.onRefreshEnd?.(); + } + }); + + // Listen to state changes + const handleStateChange = (state: RefreshState) => { + setRetryCount(state.retryCount); + setIsLoading(state.isRefreshing); + }; + + engineRef.current.on('state-change', handleStateChange); + + // Initialize with params if available + if (params) { + engineRef.current.initialize(params); + } + + return () => { + if (engineRef.current) { + engineRef.current.destroy(); + } + }; + }, []); // Empty dependency array - engine should only be created once + + // Handle parameter changes with debouncing + const updateParams = useCallback((newParams: Partial) => { + if (!paramsRef.current) return; + + const updatedParams = { ...paramsRef.current, ...newParams }; + + // Debounce parameter updates to avoid too many refreshes + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + + debounceTimerRef.current = setTimeout(() => { + setParams(updatedParams); + + if (engineRef.current) { + engineRef.current.refresh({ + type: 'parameter-change', + timestamp: Date.now(), + params: updatedParams + }).catch((err) => { + console.error('Failed to refresh quotes after parameter change:', err); + }); + } + }, debounceMs); + + }, [debounceMs]); + + // Manual refresh function + const refresh = useCallback(async (): Promise => { + if (!engineRef.current) { + throw new Error('Refresh engine not initialized'); + } + + try { + setIsLoading(true); + setError(null); + + const newQuotes = await engineRef.current.refresh({ + type: 'manual', + timestamp: Date.now() + }); + + return newQuotes; + } catch (err) { + setError(err as Error); + throw err; + } finally { + setIsLoading(false); + } + }, []); + + // Auto-refresh based on config changes + useEffect(() => { + if (engineRef.current) { + engineRef.current.updateConfig({ autoRefresh }); + + if (autoRefresh) { + engineRef.current.startAutoRefresh(); + } else { + engineRef.current.stopAutoRefresh(); + } + } + }, [autoRefresh]); + + // Update interval when changed + useEffect(() => { + if (engineRef.current) { + engineRef.current.updateConfig({ intervalMs }); + } + }, [intervalMs]); + + return { + quotes, + isLoading, + error, + lastRefreshed, + isRefreshing, + refresh, + updateParams, + retryCount + }; +} \ No newline at end of file diff --git a/apps/web/components/ui-lib/index.ts b/apps/web/components/ui-lib/index.ts index f1ef997..5aaf81b 100644 --- a/apps/web/components/ui-lib/index.ts +++ b/apps/web/components/ui-lib/index.ts @@ -1,2 +1,2 @@ -export * from './TransactionHeartbeat'; +export * from '../TransactionHeartbeat'; export * from './context/TransactionContext'; diff --git a/apps/web/services/QuoteRefreshEngine.ts b/apps/web/services/QuoteRefreshEngine.ts new file mode 100644 index 0000000..5b12dcf --- /dev/null +++ b/apps/web/services/QuoteRefreshEngine.ts @@ -0,0 +1,262 @@ +import { NormalizedQuote, QuoteRefreshConfig, RefreshState, RefreshTrigger } from '../types/quote.types'; +import { EventEmitter } from 'events'; + +export class QuoteRefreshEngine extends EventEmitter { + private refreshInterval: NodeJS.Timeout | null = null; + private config: Required; + private state: RefreshState = { + isRefreshing: false, + lastRefreshed: null, + error: null, + retryCount: 0, + quotes: [] + }; + + private abortController: AbortController | null = null; + private refreshQueue: Promise[] = []; + private lastParams: Record = {}; + + constructor( + private fetchQuotesFn: (params: any) => Promise, + config: QuoteRefreshConfig = {} + ) { + super(); + + // Default configuration + this.config = { + intervalMs: 15000, + autoRefresh: true, + maxRetries: 3, + retryDelayMs: 1000, + onRefresh: () => {}, + onError: () => {}, + onRefreshStart: () => {}, + onRefreshEnd: () => {}, + ...config + }; + } + + /** + * Initialize the refresh engine with parameters + */ + public initialize(params: Record): void { + this.lastParams = params; + + if (this.config.autoRefresh) { + this.startAutoRefresh(); + } + } + + /** + * Start auto-refresh interval + */ + public startAutoRefresh(): void { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + } + + this.refreshInterval = setInterval(() => { + this.refresh({ + type: 'interval', + timestamp: Date.now() + }); + }, this.config.intervalMs); + + // Trigger immediate first refresh + this.refresh({ + type: 'interval', + timestamp: Date.now() + }); + } + + /** + * Stop auto-refresh + */ + public stopAutoRefresh(): void { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + this.refreshInterval = null; + } + } + + /** + * Manual refresh trigger + */ + public async refresh(trigger: RefreshTrigger): Promise { + // Debounce: if already refreshing, queue this refresh + if (this.state.isRefreshing) { + return new Promise((resolve) => { + this.once('refresh-complete', (quotes: NormalizedQuote[]) => { + resolve(quotes); + }); + }); + } + + // Check if parameters changed significantly (avoid unnecessary refreshes) + if (trigger.type === 'parameter-change' && !this.hasSignificantChange(trigger.params)) { + return this.state.quotes; + } + + this.abortController = new AbortController(); + + try { + this.setState({ + ...this.state, + isRefreshing: true, + error: null + }); + + this.config.onRefreshStart(); + this.emit('refresh-start'); + + // Fetch quotes with retry logic + const quotes = await this.fetchWithRetry(trigger); + + this.setState({ + ...this.state, + isRefreshing: false, + lastRefreshed: new Date(), + error: null, + retryCount: 0, + quotes + }); + + this.config.onRefresh(quotes); + this.emit('refresh-complete', quotes); + + return quotes; + + } catch (error) { + this.setState({ + ...this.state, + isRefreshing: false, + error: error as Error + }); + + this.config.onError(error as Error); + this.emit('refresh-error', error); + + throw error; + + } finally { + this.config.onRefreshEnd(); + this.emit('refresh-end'); + this.abortController = null; + } + } + + /** + * Fetch with retry logic + */ + private async fetchWithRetry(trigger: RefreshTrigger): Promise { + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) { + try { + // Update parameters if this is a parameter change trigger + const params = trigger.type === 'parameter-change' + ? { ...this.lastParams, ...trigger.params } + : this.lastParams; + + const quotes = await this.fetchQuotesFn(params, { + signal: this.abortController?.signal + }); + + // Validate quotes + this.validateQuotes(quotes); + + return quotes; + + } catch (error) { + lastError = error as Error; + + if (attempt < this.config.maxRetries) { + // Exponential backoff + const delay = this.config.retryDelayMs * Math.pow(2, attempt - 1); + await new Promise(resolve => setTimeout(resolve, delay)); + + this.setState({ + ...this.state, + retryCount: attempt + }); + + this.emit('retry-attempt', { attempt, error }); + } + } + } + + throw lastError || new Error('Failed to fetch quotes after retries'); + } + + /** + * Check if parameters changed significantly + */ + private hasSignificantChange(newParams?: Record): boolean { + if (!newParams) return false; + + const significantParams = ['amount', 'sourceChain', 'destinationChain', 'sourceToken', 'destinationToken']; + + for (const param of significantParams) { + if (this.lastParams[param] !== newParams[param]) { + return true; + } + } + + return false; + } + + /** + * Validate quotes data structure + */ + private validateQuotes(quotes: NormalizedQuote[]): void { + quotes.forEach(quote => { + if (!quote.id || !quote.bridgeName || !quote.outputAmount) { + throw new Error('Invalid quote format: missing required fields'); + } + }); + } + + /** + * Update engine state + */ + private setState(newState: RefreshState): void { + this.state = newState; + this.emit('state-change', this.state); + } + + /** + * Get current state + */ + public getState(): RefreshState { + return { ...this.state }; + } + + /** + * Update configuration + */ + public updateConfig(config: Partial): void { + this.config = { + ...this.config, + ...config + }; + + // Restart auto-refresh if interval changed + if (config.intervalMs && this.config.autoRefresh) { + this.startAutoRefresh(); + } + } + + /** + * Clean up resources + */ + public destroy(): void { + this.stopAutoRefresh(); + + if (this.abortController) { + this.abortController.abort(); + } + + this.refreshQueue = []; + this.removeAllListeners(); + } +} \ No newline at end of file diff --git a/apps/web/types/quote.types.ts b/apps/web/types/quote.types.ts new file mode 100644 index 0000000..07e9c0c --- /dev/null +++ b/apps/web/types/quote.types.ts @@ -0,0 +1,55 @@ +export interface NormalizedQuote { + id: string; + bridgeName: string; + sourceChain: string; + destinationChain: string; + sourceToken: string; + destinationToken: string; + inputAmount: string; + outputAmount: string; + fee: { + amount: string; + currency: string; + breakdown?: { + network: string; + bridge: string; + gas: string; + }; + }; + slippage: number; + estimatedTime: number; // in seconds + liquidity: { + available: string; + total: string; + percentage: number; + }; + route: string[]; + reliability: number; // 0-100 + timestamp: number; + expiresAt: number; +} + +export interface QuoteRefreshConfig { + intervalMs?: number; // default 15 seconds + autoRefresh?: boolean; // enable/disable auto-refresh + maxRetries?: number; // maximum retry attempts + retryDelayMs?: number; // delay between retries + onRefresh?: (quotes: NormalizedQuote[]) => void; + onError?: (error: Error) => void; + onRefreshStart?: () => void; + onRefreshEnd?: () => void; +} + +export interface RefreshState { + isRefreshing: boolean; + lastRefreshed: Date | null; + error: Error | null; + retryCount: number; + quotes: NormalizedQuote[]; +} + +export interface RefreshTrigger { + type: 'interval' | 'manual' | 'parameter-change'; + timestamp: number; + params?: Record; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 1c704c2..d22b6c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,7 +33,7 @@ "include": [ "src/**/*", "libs/**/src/**/*" - ], +, "apps/web/services", "apps/web/components/ui-lib/hooks/useBridgeQuotes.ts" ], "exclude": [ "apps", "examples", From 0ab71b61585eeff01524a4115b8153d91155aa7d Mon Sep 17 00:00:00 2001 From: feyishola Date: Sun, 22 Feb 2026 06:26:10 +0100 Subject: [PATCH 2/2] npm error fixed --- package-lock.json | 131 +++++++++++++++++++--------------------------- package.json | 2 +- 2 files changed, 55 insertions(+), 78 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26c9a81..8c368a5 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", @@ -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", @@ -2025,7 +2025,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 +2046,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 +2070,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": { @@ -2384,22 +2384,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,42 +2416,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==", + "node_modules/@nestjs/swagger/node_modules/lodash": { + "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/@nestjs/testing": { @@ -2568,6 +2536,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 +2639,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": { @@ -2889,7 +2864,7 @@ "version": "22.19.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3754,7 +3729,7 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -3790,7 +3765,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -3973,7 +3948,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": { @@ -4917,7 +4892,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,7 +5043,7 @@ "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" @@ -7468,7 +7443,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" @@ -7724,7 +7698,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": { @@ -9524,10 +9498,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", @@ -10002,7 +9979,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", @@ -10362,7 +10339,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", @@ -10438,7 +10415,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 +10542,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": { @@ -10988,7 +10965,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",