From 1b71fc87ab0f21b8012aa5d922e9ec8966e0f151 Mon Sep 17 00:00:00 2001 From: Bojan Kucera Date: Tue, 3 Jun 2025 11:37:58 -0400 Subject: [PATCH] no idea- added loki and other stuff to market-data-gateway, also added config lib --- .env.example | 9 + .../market-data-gateway/package.json | 2 +- .../src/config/DataProviderConfig.ts | 114 ++ .../market-data-gateway/src/index.ts | 321 +++++- .../src/services/AdvancedCache.ts | 361 ++++++ .../src/services/ConnectionPoolManager.ts | 346 ++++++ .../src/services/DataNormalizer.ts | 345 +++++- .../src/services/DataSourceManager.ts | 47 +- .../src/services/EventPublisher.ts | 6 +- .../src/services/MarketDataGatewayService.ts | 18 +- .../market-data-gateway/README.md | 0 .../market-data-gateway/package.json | 0 .../src/controllers/GatewayController.ts | 0 .../src/controllers/HealthController.ts | 0 .../src/controllers/MetricsController.ts | 0 .../market-data-gateway/src/index.ts | 0 .../src/services/CacheManager.ts | 0 .../src/services/DataSourceManager.ts | 0 .../src/services/MarketDataGatewayService.ts | 0 .../src/services/MetricsCollector.ts | 0 .../src/services/ProcessingEngine.ts | 0 .../src/services/ServiceIntegrationManager.ts | 0 .../src/services/SubscriptionManager.ts | 0 .../src/types/MarketDataGateway.ts | 0 .../market-data-gateway/tsconfig.json | 0 bun.lock | 1009 ++++++++++++++++- docker-compose.monitoring.yml | 46 + docker-compose.monitoring.yml.old | 46 + docker-compose.yml | 38 +- docker-compose.yml.backup | 244 ++++ docker-compose.yml.new | 217 ++++ docs/loki-logging.md | 171 +++ libs/config/.env.example | 53 + libs/config/.env.production.example | 28 + libs/config/README.md | 103 ++ libs/config/package.json | 37 + libs/config/setup.bat | 24 + libs/config/src/core.test.ts | 113 ++ libs/config/src/core.ts | 162 +++ libs/config/src/data-providers.ts | 73 ++ libs/config/src/database.ts | 52 + libs/config/src/example.ts | 73 ++ libs/config/src/index.ts | 24 + libs/config/src/logging.ts | 75 ++ libs/config/src/risk.ts | 36 + libs/config/src/services/index.ts | 5 + .../src/services/market-data-gateway.ts | 106 ++ libs/config/src/services/risk-guardian.ts | 112 ++ libs/config/src/types.ts | 87 ++ libs/config/tsconfig.json | 10 + libs/http-client/README.md | 187 +++ libs/http-client/examples/basic-usage.ts | 91 ++ libs/http-client/package.json | 34 + libs/http-client/src/BunHttpClient.test.ts | 182 +++ libs/http-client/src/BunHttpClient.ts | 199 ++++ libs/http-client/src/ConnectionPool.ts | 331 ++++++ libs/http-client/src/RetryHandler.ts | 131 +++ libs/http-client/src/index.ts | 25 + libs/http-client/src/types.ts | 104 ++ libs/http-client/tsconfig.json | 12 + libs/utils/package.json | 4 +- libs/utils/src/index.ts | 1 + libs/utils/src/logger.ts | 73 +- libs/utils/src/lokiClient.ts | 86 ++ .../dashboards/json/stock-bot-logs.json | 211 ++++ .../provisioning/dashboards/stockbot.yml | 11 + .../grafana/provisioning/datasources/loki.yml | 10 + monitoring/loki/loki-config.yaml | 41 + package.json | 13 +- scripts/docker.ps1 | 11 +- tools/check-loki-status.bat | 27 + tools/test-loki-logging.ts | 34 + 72 files changed, 6178 insertions(+), 153 deletions(-) create mode 100644 apps/core-services/market-data-gateway/src/config/DataProviderConfig.ts create mode 100644 apps/core-services/market-data-gateway/src/services/AdvancedCache.ts create mode 100644 apps/core-services/market-data-gateway/src/services/ConnectionPoolManager.ts delete mode 100644 apps/data-services/market-data-gateway/README.md delete mode 100644 apps/data-services/market-data-gateway/package.json delete mode 100644 apps/data-services/market-data-gateway/src/controllers/GatewayController.ts delete mode 100644 apps/data-services/market-data-gateway/src/controllers/HealthController.ts delete mode 100644 apps/data-services/market-data-gateway/src/controllers/MetricsController.ts delete mode 100644 apps/data-services/market-data-gateway/src/index.ts delete mode 100644 apps/data-services/market-data-gateway/src/services/CacheManager.ts delete mode 100644 apps/data-services/market-data-gateway/src/services/DataSourceManager.ts delete mode 100644 apps/data-services/market-data-gateway/src/services/MarketDataGatewayService.ts delete mode 100644 apps/data-services/market-data-gateway/src/services/MetricsCollector.ts delete mode 100644 apps/data-services/market-data-gateway/src/services/ProcessingEngine.ts delete mode 100644 apps/data-services/market-data-gateway/src/services/ServiceIntegrationManager.ts delete mode 100644 apps/data-services/market-data-gateway/src/services/SubscriptionManager.ts delete mode 100644 apps/data-services/market-data-gateway/src/types/MarketDataGateway.ts delete mode 100644 apps/data-services/market-data-gateway/tsconfig.json create mode 100644 docker-compose.monitoring.yml create mode 100644 docker-compose.monitoring.yml.old create mode 100644 docker-compose.yml.backup create mode 100644 docker-compose.yml.new create mode 100644 docs/loki-logging.md create mode 100644 libs/config/.env.example create mode 100644 libs/config/.env.production.example create mode 100644 libs/config/README.md create mode 100644 libs/config/package.json create mode 100644 libs/config/setup.bat create mode 100644 libs/config/src/core.test.ts create mode 100644 libs/config/src/core.ts create mode 100644 libs/config/src/data-providers.ts create mode 100644 libs/config/src/database.ts create mode 100644 libs/config/src/example.ts create mode 100644 libs/config/src/index.ts create mode 100644 libs/config/src/logging.ts create mode 100644 libs/config/src/risk.ts create mode 100644 libs/config/src/services/index.ts create mode 100644 libs/config/src/services/market-data-gateway.ts create mode 100644 libs/config/src/services/risk-guardian.ts create mode 100644 libs/config/src/types.ts create mode 100644 libs/config/tsconfig.json create mode 100644 libs/http-client/README.md create mode 100644 libs/http-client/examples/basic-usage.ts create mode 100644 libs/http-client/package.json create mode 100644 libs/http-client/src/BunHttpClient.test.ts create mode 100644 libs/http-client/src/BunHttpClient.ts create mode 100644 libs/http-client/src/ConnectionPool.ts create mode 100644 libs/http-client/src/RetryHandler.ts create mode 100644 libs/http-client/src/index.ts create mode 100644 libs/http-client/src/types.ts create mode 100644 libs/http-client/tsconfig.json create mode 100644 libs/utils/src/lokiClient.ts create mode 100644 monitoring/grafana/provisioning/dashboards/json/stock-bot-logs.json create mode 100644 monitoring/grafana/provisioning/dashboards/stockbot.yml create mode 100644 monitoring/grafana/provisioning/datasources/loki.yml create mode 100644 monitoring/loki/loki-config.yaml create mode 100644 tools/check-loki-status.bat create mode 100644 tools/test-loki-logging.ts diff --git a/.env.example b/.env.example index 15bc837..aa9c632 100644 --- a/.env.example +++ b/.env.example @@ -29,6 +29,15 @@ MAX_DAILY_LOSS=1000 # Logging LOG_LEVEL=debug +LOG_CONSOLE=true +LOKI_HOST=localhost +LOKI_PORT=3100 +LOKI_USERNAME= +LOKI_PASSWORD= +LOKI_RETENTION_DAYS=30 +LOKI_LABELS=environment=development,service=stock-bot +LOKI_BATCH_SIZE=100 +LOKI_FLUSH_INTERVAL_MS=5000 # Feature Flags ENABLE_ML_SIGNALS=false diff --git a/apps/core-services/market-data-gateway/package.json b/apps/core-services/market-data-gateway/package.json index f6dce98..fbdc269 100644 --- a/apps/core-services/market-data-gateway/package.json +++ b/apps/core-services/market-data-gateway/package.json @@ -16,7 +16,6 @@ "hono": "^4.6.3", "@hono/node-server": "^1.8.0", "ws": "^8.18.0", - "axios": "^1.6.0", "bull": "^4.12.0", "ioredis": "^5.4.1", "zod": "^3.22.0", @@ -29,6 +28,7 @@ "fast-json-stringify": "^5.10.0", "pino": "^8.17.0", "dotenv": "^16.3.0", + "@stock-bot/http-client": "*", "@stock-bot/config": "*", "@stock-bot/shared-types": "*", "@stock-bot/event-bus": "*", diff --git a/apps/core-services/market-data-gateway/src/config/DataProviderConfig.ts b/apps/core-services/market-data-gateway/src/config/DataProviderConfig.ts new file mode 100644 index 0000000..60d11cb --- /dev/null +++ b/apps/core-services/market-data-gateway/src/config/DataProviderConfig.ts @@ -0,0 +1,114 @@ +// Data Provider Configuration +export interface DataProviderConfig { + name: string; + type: 'rest' | 'websocket' | 'both'; + enabled: boolean; + endpoints: { + quotes?: string; + candles?: string; + trades?: string; + websocket?: string; + }; + authentication?: { + type: 'api_key' | 'bearer' | 'basic'; + key?: string; + secret?: string; + token?: string; + }; + rateLimits: { + requestsPerSecond: number; + requestsPerMinute: number; + requestsPerHour: number; + }; + retryPolicy: { + maxRetries: number; + backoffMultiplier: number; + initialDelayMs: number; + }; + timeout: number; + priority: number; // 1-10, higher is better +} + +export const dataProviderConfigs: Record = { + 'alpha-vantage': { + name: 'Alpha Vantage', + type: 'rest', + enabled: true, + endpoints: { + quotes: 'https://www.alphavantage.co/query', + candles: 'https://www.alphavantage.co/query', + }, + authentication: { + type: 'api_key', + key: process.env.ALPHA_VANTAGE_API_KEY, + }, + rateLimits: { + requestsPerSecond: 5, + requestsPerMinute: 500, + requestsPerHour: 25000, + }, + retryPolicy: { + maxRetries: 3, + backoffMultiplier: 2, + initialDelayMs: 1000, + }, + timeout: 10000, + priority: 7, + }, + 'yahoo-finance': { + name: 'Yahoo Finance', + type: 'rest', + enabled: true, + endpoints: { + quotes: 'https://query1.finance.yahoo.com/v8/finance/chart', + candles: 'https://query1.finance.yahoo.com/v8/finance/chart', + }, + rateLimits: { + requestsPerSecond: 10, + requestsPerMinute: 2000, + requestsPerHour: 100000, + }, + retryPolicy: { + maxRetries: 3, + backoffMultiplier: 1.5, + initialDelayMs: 500, + }, + timeout: 8000, + priority: 8, + }, + 'polygon': { + name: 'Polygon.io', + type: 'both', + enabled: false, + endpoints: { + quotes: 'https://api.polygon.io/v2/last/nbbo', + candles: 'https://api.polygon.io/v2/aggs/ticker', + trades: 'https://api.polygon.io/v3/trades', + websocket: 'wss://socket.polygon.io/stocks', + }, + authentication: { + type: 'api_key', + key: process.env.POLYGON_API_KEY, + }, + rateLimits: { + requestsPerSecond: 100, + requestsPerMinute: 5000, + requestsPerHour: 100000, + }, + retryPolicy: { + maxRetries: 5, + backoffMultiplier: 2, + initialDelayMs: 200, + }, + timeout: 5000, + priority: 9, + }, +}; + +export function getEnabledProviders(): DataProviderConfig[] { + return Object.values(dataProviderConfigs).filter(config => config.enabled); +} + +export function getProviderByPriority(): DataProviderConfig[] { + return getEnabledProviders().sort((a, b) => b.priority - a.priority); +} diff --git a/apps/core-services/market-data-gateway/src/index.ts b/apps/core-services/market-data-gateway/src/index.ts index 6da5aa2..a664f25 100644 --- a/apps/core-services/market-data-gateway/src/index.ts +++ b/apps/core-services/market-data-gateway/src/index.ts @@ -1,4 +1,4 @@ -// Market Data Gateway - Unified Implementation +// Market Data Gateway - Enhanced Implementation import { Hono } from 'hono'; import { cors } from 'hono/cors'; import { logger } from 'hono/logger'; @@ -8,6 +8,12 @@ import { WebSocketServer } from 'ws'; // Types import { GatewayConfig } from './types/MarketDataGateway'; +// Services +import { DataNormalizer, DataNormalizationResult } from './services/DataNormalizer'; +import { MarketDataCache } from './services/AdvancedCache'; +import { ConnectionPoolManager } from './services/ConnectionPoolManager'; +import { dataProviderConfigs, getEnabledProviders } from './config/DataProviderConfig'; + // Simple logger interface interface Logger { info: (message: string, ...args: any[]) => void; @@ -18,12 +24,26 @@ interface Logger { // Create application logger const appLogger: Logger = { - info: (message: string, ...args: any[]) => console.log(`[MDG-UNIFIED] [INFO] ${message}`, ...args), - error: (message: string, ...args: any[]) => console.error(`[MDG-UNIFIED] [ERROR] ${message}`, ...args), - warn: (message: string, ...args: any[]) => console.warn(`[MDG-UNIFIED] [WARN] ${message}`, ...args), - debug: (message: string, ...args: any[]) => console.debug(`[MDG-UNIFIED] [DEBUG] ${message}`, ...args), + info: (message: string, ...args: any[]) => console.log(`[MDG-ENHANCED] [INFO] ${message}`, ...args), + error: (message: string, ...args: any[]) => console.error(`[MDG-ENHANCED] [ERROR] ${message}`, ...args), + warn: (message: string, ...args: any[]) => console.warn(`[MDG-ENHANCED] [WARN] ${message}`, ...args), + debug: (message: string, ...args: any[]) => console.debug(`[MDG-ENHANCED] [DEBUG] ${message}`, ...args), }; +// Initialize services +const dataNormalizer = new DataNormalizer(); +const marketDataCache = new MarketDataCache(); +const connectionPool = new ConnectionPoolManager({ + maxConnections: 100, + maxConnectionsPerHost: 20, + connectionTimeout: 10000, + requestTimeout: 30000, + retryAttempts: 3, + retryDelay: 1000, + keepAlive: true, + maxIdleTime: 300000, // 5 minutes +}); + // Configuration matching the GatewayConfig interface const config: GatewayConfig = { server: { @@ -36,7 +56,46 @@ const config: GatewayConfig = { headers: ['Content-Type', 'Authorization'], }, }, - dataSources: [], // Array of DataSourceConfig, initially empty + dataSources: getEnabledProviders().map(provider => ({ + id: provider.name.toLowerCase().replace(/\s+/g, '-'), + name: provider.name, + type: provider.type === 'both' ? 'websocket' : provider.type as any, + enabled: provider.enabled, + priority: provider.priority, + rateLimit: { + requestsPerSecond: provider.rateLimits.requestsPerSecond, + burstLimit: provider.rateLimits.requestsPerMinute, + }, + connection: { + url: provider.endpoints.quotes || provider.endpoints.websocket || '', + authentication: provider.authentication ? { + type: provider.authentication.type === 'api_key' ? 'apikey' as const : 'basic' as const, + credentials: { + apiKey: provider.authentication.key || '', + secret: provider.authentication.secret || '', + token: provider.authentication.token || '', + }, + } : undefined, + }, + subscriptions: { + quotes: true, + trades: true, + orderbook: provider.endpoints.websocket ? true : false, + candles: true, + news: false, + }, + symbols: ['AAPL', 'GOOGL', 'MSFT', 'AMZN', 'TSLA'], // Default symbols + retryPolicy: { + maxRetries: provider.retryPolicy.maxRetries, + backoffMultiplier: provider.retryPolicy.backoffMultiplier, + maxBackoffMs: provider.retryPolicy.initialDelayMs * 10, + }, + healthCheck: { + intervalMs: 30000, + timeoutMs: provider.timeout, + expectedLatencyMs: 1000, + }, + })), processing: { pipelines: [], bufferSize: 10000, @@ -207,32 +266,134 @@ app.post('/api/v1/subscriptions', async (c) => { } }); -// Market data endpoints +// Market data endpoints with enhanced functionality app.get('/api/v1/data/tick/:symbol', async (c) => { - const symbol = c.req.param('symbol'); - return c.json({ - ...mockTickData, - symbol: symbol.toUpperCase(), - }); + const symbol = c.req.param('symbol').toUpperCase(); + const source = c.req.query('source') || 'yahoo-finance'; + + try { + // Check cache first + const cacheKey = marketDataCache.getQuoteKey(symbol); + const cachedData = marketDataCache.get(cacheKey); + + if (cachedData) { + appLogger.debug(`Cache hit for ${symbol}`); + return c.json({ + ...cachedData, + cached: true, + timestamp: new Date().toISOString(), + }); + } + + // Fetch from provider + const provider = dataProviderConfigs[source]; + if (!provider || !provider.enabled) { + return c.json({ error: 'Data source not available' }, 400); + } + + // Mock data for now (replace with actual API calls) + const mockData = { + symbol, + price: 150.25 + (Math.random() - 0.5) * 10, + volume: Math.floor(Math.random() * 100000), + timestamp: new Date().toISOString(), + bid: 150.20, + ask: 150.30, + source, + }; + + // Normalize the data + const normalizedResult = dataNormalizer.normalizeMarketData(mockData, source); + + if (!normalizedResult.success) { + return c.json({ error: normalizedResult.error }, 500); + } + + // Cache the result + marketDataCache.setQuote(symbol, normalizedResult.data); + + return c.json({ + ...normalizedResult.data, + cached: false, + processingTimeMs: normalizedResult.processingTimeMs, + }); + + } catch (error) { + appLogger.error(`Error fetching tick data for ${symbol}:`, error); + return c.json({ error: 'Internal server error' }, 500); + } }); app.get('/api/v1/data/candles/:symbol', async (c) => { - const symbol = c.req.param('symbol'); + const symbol = c.req.param('symbol').toUpperCase(); const timeframe = c.req.query('timeframe') || '1m'; - const limit = parseInt(c.req.query('limit') || '100'); + const limit = Math.min(parseInt(c.req.query('limit') || '100'), 1000); // Max 1000 + const source = c.req.query('source') || 'yahoo-finance'; - const candles = Array.from({ length: limit }, (_, i) => ({ - ...mockCandleData, - symbol: symbol.toUpperCase(), - timeframe, - timestamp: new Date(Date.now() - i * 60000).toISOString(), - })); - - return c.json({ candles }); + try { + // Generate cache key + const cacheKey = `candles:${symbol}:${timeframe}:${limit}`; + const cachedData = marketDataCache.get(cacheKey); + + if (cachedData) { + appLogger.debug(`Cache hit for candles ${symbol}:${timeframe}`); + return c.json({ + candles: cachedData, + cached: true, + count: cachedData.length, + }); + } + + // Mock candle data generation (replace with actual API calls) + const candles = Array.from({ length: limit }, (_, i) => { + const timestamp = new Date(Date.now() - i * 60000); + const basePrice = 150 + (Math.random() - 0.5) * 20; + const variation = (Math.random() - 0.5) * 2; + + return { + symbol, + timeframe, + timestamp: timestamp.toISOString(), + open: basePrice + variation, + high: basePrice + variation + Math.random() * 2, + low: basePrice + variation - Math.random() * 2, + close: basePrice + variation + (Math.random() - 0.5), + volume: Math.floor(Math.random() * 10000), + source, + }; + }).reverse(); // Oldest first + + // Normalize OHLCV data + const normalizedResult = dataNormalizer.normalizeOHLCV( + { candles: candles.map(c => ({ ...c, timestamp: new Date(c.timestamp) })) }, + source + ); + + if (!normalizedResult.success) { + return c.json({ error: normalizedResult.error }, 500); + } + + // Cache the result + marketDataCache.set(cacheKey, normalizedResult.data, marketDataCache['getCandleTTL'](timeframe)); + + return c.json({ + candles: normalizedResult.data, + cached: false, + count: normalizedResult.data?.length || 0, + processingTimeMs: normalizedResult.processingTimeMs, + }); + + } catch (error) { + appLogger.error(`Error fetching candles for ${symbol}:`, error); + return c.json({ error: 'Internal server error' }, 500); + } }); -// Metrics endpoints +// Enhanced metrics endpoints app.get('/api/v1/metrics', async (c) => { + const cacheStats = marketDataCache.getStats(); + const connectionStats = connectionPool.getStats(); + return c.json({ system: { uptime: process.uptime(), @@ -241,9 +402,91 @@ app.get('/api/v1/metrics', async (c) => { }, gateway: { activeConnections: webSocketServer ? webSocketServer.clients.size : 0, - dataSourcesCount: config.dataSources.length, + dataSourcesCount: config.dataSources.filter(ds => ds.enabled).length, messagesProcessed: 0, }, + cache: cacheStats, + connectionPool: connectionStats, + timestamp: new Date().toISOString(), + }); +}); + +// Data quality assessment endpoint +app.get('/api/v1/data/quality/:symbol', async (c) => { + const symbol = c.req.param('symbol').toUpperCase(); + const source = c.req.query('source') || 'yahoo-finance'; + + try { + // Get recent data for quality assessment (mock for now) + const recentData = Array.from({ length: 10 }, (_, i) => ({ + symbol, + price: 150 + (Math.random() - 0.5) * 10, + bid: 149.5, + ask: 150.5, + volume: Math.floor(Math.random() * 10000), + timestamp: new Date(Date.now() - i * 60000), + })); + + const qualityMetrics = dataNormalizer.assessDataQuality(recentData, source); + + return c.json({ + symbol, + source, + dataPoints: recentData.length, + qualityMetrics, + timestamp: new Date().toISOString(), + }); + + } catch (error) { + appLogger.error(`Error assessing data quality for ${symbol}:`, error); + return c.json({ error: 'Internal server error' }, 500); + } +}); + +// Cache management endpoints +app.get('/api/v1/cache/stats', async (c) => { + return c.json({ + stats: marketDataCache.getStats(), + keys: marketDataCache.keys().slice(0, 100), // Limit to first 100 keys + timestamp: new Date().toISOString(), + }); +}); + +app.delete('/api/v1/cache/clear', async (c) => { + marketDataCache.clear(); + return c.json({ + message: 'Cache cleared successfully', + timestamp: new Date().toISOString(), + }); +}); + +app.delete('/api/v1/cache/key/:key', async (c) => { + const key = c.req.param('key'); + const deleted = marketDataCache.delete(key); + + return c.json({ + message: deleted ? 'Key deleted successfully' : 'Key not found', + key, + deleted, + timestamp: new Date().toISOString(), + }); +}); + +// Data providers status endpoint +app.get('/api/v1/providers', async (c) => { + const providers = Object.values(dataProviderConfigs).map(provider => ({ + name: provider.name, + enabled: provider.enabled, + type: provider.type, + priority: provider.priority, + rateLimits: provider.rateLimits, + endpoints: Object.keys(provider.endpoints), + })); + + return c.json({ + providers, + enabled: providers.filter(p => p.enabled).length, + total: providers.length, timestamp: new Date().toISOString(), }); }); @@ -332,7 +575,7 @@ function setupWebSocketServer(): void { appLogger.info(`WebSocket server listening on port ${wsPort}`); } -// Graceful shutdown handler +// Enhanced graceful shutdown handler async function gracefulShutdown(): Promise { if (isShuttingDown) return; isShuttingDown = true; @@ -349,6 +592,14 @@ async function gracefulShutdown(): Promise { appLogger.info('WebSocket server closed'); } + // Close connection pool + await connectionPool.close(); + appLogger.info('Connection pool closed'); + + // Clean up cache + marketDataCache.destroy(); + appLogger.info('Cache destroyed'); + appLogger.info('Graceful shutdown completed'); process.exit(0); } catch (error) { @@ -357,10 +608,19 @@ async function gracefulShutdown(): Promise { } } -// Start server function +// Enhanced start server function async function startServer(): Promise { try { - appLogger.info('Starting Market Data Gateway...'); + appLogger.info('Starting Enhanced Market Data Gateway...'); + + // Initialize cache event listeners + marketDataCache.on('hit', (key) => appLogger.debug(`Cache hit: ${key}`)); + marketDataCache.on('miss', (key) => appLogger.debug(`Cache miss: ${key}`)); + marketDataCache.on('evict', (key) => appLogger.debug(`Cache evict: ${key}`)); + + // Initialize connection pool event listeners + connectionPool.on('connectionCreated', (host) => appLogger.debug(`Connection created for: ${host}`)); + connectionPool.on('error', ({ host, error }) => appLogger.warn(`Connection error for ${host}: ${error}`)); // Setup WebSocket server setupWebSocketServer(); @@ -369,9 +629,14 @@ async function startServer(): Promise { process.on('SIGTERM', gracefulShutdown); process.on('SIGINT', gracefulShutdown); + // Log service status appLogger.info(`HTTP server starting on ${config.server.host}:${config.server.port}`); appLogger.info(`WebSocket server running on port ${config.server.port + 1}`); - appLogger.info('Market Data Gateway started successfully'); + appLogger.info(`Data sources configured: ${config.dataSources.length}`); + appLogger.info(`Enabled providers: ${config.dataSources.filter(ds => ds.enabled).length}`); + appLogger.info(`Cache max size: ${marketDataCache['config'].maxSize}`); + appLogger.info(`Connection pool max connections: ${connectionPool['config'].maxConnections}`); + appLogger.info('Enhanced Market Data Gateway started successfully'); } catch (error) { appLogger.error('Failed to start server:', error); diff --git a/apps/core-services/market-data-gateway/src/services/AdvancedCache.ts b/apps/core-services/market-data-gateway/src/services/AdvancedCache.ts new file mode 100644 index 0000000..e52bb34 --- /dev/null +++ b/apps/core-services/market-data-gateway/src/services/AdvancedCache.ts @@ -0,0 +1,361 @@ +import { EventEmitter } from 'events'; + +export interface CacheEntry { + data: T; + timestamp: number; + ttl: number; + hits: number; + lastAccessed: number; +} + +export interface CacheStats { + totalEntries: number; + memoryUsage: number; + hitRate: number; + totalHits: number; + totalMisses: number; + averageAccessTime: number; +} + +export interface CacheConfig { + maxSize: number; + defaultTtl: number; + cleanupInterval: number; + enableStats: boolean; + compressionEnabled: boolean; +} + +export class AdvancedCache extends EventEmitter { + private cache = new Map>(); + private stats = { + hits: 0, + misses: 0, + totalAccessTime: 0, + accessCount: 0, + }; + private cleanupTimer: NodeJS.Timeout | null = null; + + constructor(private config: CacheConfig) { + super(); + this.startCleanupTimer(); + } + + /** + * Get value from cache + */ + get(key: string): T | null { + const startTime = Date.now(); + const entry = this.cache.get(key); + + if (!entry) { + this.stats.misses++; + this.emit('miss', key); + return null; + } + + // Check if entry has expired + if (Date.now() > entry.timestamp + entry.ttl) { + this.cache.delete(key); + this.stats.misses++; + this.emit('expired', key, entry); + return null; + } + + // Update access statistics + entry.hits++; + entry.lastAccessed = Date.now(); + this.stats.hits++; + + if (this.config.enableStats) { + this.stats.totalAccessTime += Date.now() - startTime; + this.stats.accessCount++; + } + + this.emit('hit', key, entry); + return entry.data; + } + + /** + * Set value in cache + */ + set(key: string, value: T, ttl?: number): void { + const effectiveTtl = ttl || this.config.defaultTtl; + + // Check cache size limits + if (this.cache.size >= this.config.maxSize && !this.cache.has(key)) { + this.evictLeastUsed(); + } + + const entry: CacheEntry = { + data: value, + timestamp: Date.now(), + ttl: effectiveTtl, + hits: 0, + lastAccessed: Date.now(), + }; + + this.cache.set(key, entry); + this.emit('set', key, entry); + } + + /** + * Delete value from cache + */ + delete(key: string): boolean { + const deleted = this.cache.delete(key); + if (deleted) { + this.emit('delete', key); + } + return deleted; + } + + /** + * Check if key exists in cache + */ + has(key: string): boolean { + const entry = this.cache.get(key); + if (!entry) return false; + + // Check if expired + if (Date.now() > entry.timestamp + entry.ttl) { + this.cache.delete(key); + return false; + } + + return true; + } + + /** + * Clear all cache entries + */ + clear(): void { + this.cache.clear(); + this.resetStats(); + this.emit('clear'); + } + + /** + * Get cache statistics + */ + getStats(): CacheStats { + const memoryUsage = this.estimateMemoryUsage(); + const hitRate = this.stats.hits + this.stats.misses > 0 + ? this.stats.hits / (this.stats.hits + this.stats.misses) + : 0; + const averageAccessTime = this.stats.accessCount > 0 + ? this.stats.totalAccessTime / this.stats.accessCount + : 0; + + return { + totalEntries: this.cache.size, + memoryUsage, + hitRate, + totalHits: this.stats.hits, + totalMisses: this.stats.misses, + averageAccessTime, + }; + } + + /** + * Get all cache keys + */ + keys(): string[] { + return Array.from(this.cache.keys()); + } + + /** + * Get cache size + */ + size(): number { + return this.cache.size; + } + + /** + * Get or set with async loader function + */ + async getOrSet( + key: string, + loader: () => Promise, + ttl?: number + ): Promise { + const cached = this.get(key) as K; + if (cached !== null) { + return cached; + } + + try { + const value = await loader(); + this.set(key, value as any, ttl); + return value; + } catch (error) { + this.emit('error', key, error); + throw error; + } + } + + /** + * Batch get multiple keys + */ + mget(keys: string[]): Map { + const result = new Map(); + for (const key of keys) { + result.set(key, this.get(key)); + } + return result; + } + + /** + * Batch set multiple key-value pairs + */ + mset(entries: Map, ttl?: number): void { + for (const [key, value] of entries) { + this.set(key, value, ttl); + } + } + + /** + * Clean up expired entries + */ + cleanup(): number { + const now = Date.now(); + let removedCount = 0; + + for (const [key, entry] of this.cache.entries()) { + if (now > entry.timestamp + entry.ttl) { + this.cache.delete(key); + removedCount++; + this.emit('expired', key, entry); + } + } + + return removedCount; + } + + /** + * Evict least recently used entries + */ + private evictLeastUsed(): void { + let oldestKey: string | null = null; + let oldestTime = Date.now(); + + for (const [key, entry] of this.cache.entries()) { + if (entry.lastAccessed < oldestTime) { + oldestTime = entry.lastAccessed; + oldestKey = key; + } + } + + if (oldestKey) { + this.cache.delete(oldestKey); + this.emit('evict', oldestKey); + } + } + + /** + * Estimate memory usage in bytes + */ + private estimateMemoryUsage(): number { + let totalSize = 0; + + for (const [key, entry] of this.cache.entries()) { + // Rough estimation: key size + data size (as JSON string) + totalSize += key.length * 2; // UTF-16 encoding + totalSize += JSON.stringify(entry.data).length * 2; + totalSize += 64; // Overhead for entry metadata + } + + return totalSize; + } + + /** + * Reset statistics + */ + private resetStats(): void { + this.stats = { + hits: 0, + misses: 0, + totalAccessTime: 0, + accessCount: 0, + }; + } + + /** + * Start cleanup timer + */ + private startCleanupTimer(): void { + if (this.cleanupTimer) { + clearInterval(this.cleanupTimer); + } + + this.cleanupTimer = setInterval(() => { + const removed = this.cleanup(); + if (removed > 0) { + this.emit('cleanup', removed); + } + }, this.config.cleanupInterval); + } + + /** + * Stop cleanup timer and close cache + */ + destroy(): void { + if (this.cleanupTimer) { + clearInterval(this.cleanupTimer); + this.cleanupTimer = null; + } + this.clear(); + this.removeAllListeners(); + } +} + +// Specialized cache for market data +export class MarketDataCache extends AdvancedCache { + constructor() { + super({ + maxSize: 10000, + defaultTtl: 60000, // 1 minute + cleanupInterval: 30000, // 30 seconds + enableStats: true, + compressionEnabled: false, + }); + } + + // Market data specific cache keys + getQuoteKey(symbol: string): string { + return `quote:${symbol}`; + } + + getCandleKey(symbol: string, timeframe: string, timestamp: Date): string { + return `candle:${symbol}:${timeframe}:${timestamp.getTime()}`; + } + + getOrderBookKey(symbol: string): string { + return `orderbook:${symbol}`; + } + + // Market data specific TTLs + setQuote(symbol: string, data: any): void { + this.set(this.getQuoteKey(symbol), data, 60000); // 1 minute + } + + setCandle(symbol: string, timeframe: string, timestamp: Date, data: any): void { + const ttl = this.getCandleTTL(timeframe); + this.set(this.getCandleKey(symbol, timeframe, timestamp), data, ttl); + } + + setOrderBook(symbol: string, data: any): void { + this.set(this.getOrderBookKey(symbol), data, 30000); // 30 seconds + } + + private getCandleTTL(timeframe: string): number { + const ttlMap: Record = { + '1m': 60000, // 1 minute + '5m': 300000, // 5 minutes + '15m': 900000, // 15 minutes + '1h': 3600000, // 1 hour + '1d': 86400000, // 24 hours + }; + + return ttlMap[timeframe] || 300000; // Default 5 minutes + } +} diff --git a/apps/core-services/market-data-gateway/src/services/ConnectionPoolManager.ts b/apps/core-services/market-data-gateway/src/services/ConnectionPoolManager.ts new file mode 100644 index 0000000..97e1773 --- /dev/null +++ b/apps/core-services/market-data-gateway/src/services/ConnectionPoolManager.ts @@ -0,0 +1,346 @@ +import { EventEmitter } from 'eventemitter3'; +import { + BunHttpClient, + RequestConfig, + HttpResponse, + ConnectionStats, + HttpClientConfig +} from '@stock-bot/http-client'; + +export interface ConnectionPoolConfig { + maxConnections: number; + maxConnectionsPerHost: number; + connectionTimeout: number; + requestTimeout: number; + retryAttempts: number; + retryDelay: number; + keepAlive: boolean; + maxIdleTime: number; +} + +export interface QueuedRequest { + id: string; + config: RequestConfig; + resolve: (value: any) => void; + reject: (error: any) => void; + timestamp: number; + retryCount: number; +} + +export class ConnectionPoolManager extends EventEmitter { + private clients = new Map(); + private activeRequests = new Map(); // host -> count + private requestQueue: QueuedRequest[] = []; + private stats = { + totalConnections: 0, + successfulRequests: 0, + failedRequests: 0, + totalResponseTime: 0, + requestCount: 0, + }; + private isProcessingQueue = false; + private queueProcessor?: NodeJS.Timeout; + + constructor(private config: ConnectionPoolConfig) { + super(); + this.startQueueProcessor(); + } + + /** + * Get or create a client for a host + */ + private getClient(host: string): BunHttpClient { + if (!this.clients.has(host)) { + const client = new BunHttpClient({ + baseURL: `https://${host}`, + timeout: this.config.requestTimeout, + retries: this.config.retryAttempts, + retryDelay: this.config.retryDelay, + keepAlive: this.config.keepAlive, + headers: { + 'User-Agent': 'StockBot-MarketDataGateway/1.0', + 'Accept': 'application/json', + }, + validateStatus: (status: number) => status < 500, + }); + + // Listen for events from the client + client.on('response', (data) => { + const responseTime = data.response.timing.duration; + this.updateStats(true, responseTime); + this.emit('response', { + host, + responseTime, + status: data.response.status + }); + }); + + client.on('error', (data) => { + const responseTime = data.error?.config?.metadata?.startTime + ? Date.now() - data.error.config.metadata.startTime + : 0; + + this.updateStats(false, responseTime); + this.emit('error', { + host, + error: data.error.message, + responseTime + }); + }); + + this.clients.set(host, client); + this.activeRequests.set(host, 0); + this.stats.totalConnections++; + + this.emit('connectionCreated', host); + } + + return this.clients.get(host)!; + } + + /** + * Make an HTTP request with connection pooling + */ + async request(config: RequestConfig): Promise { + return new Promise((resolve, reject) => { + const requestId = this.generateRequestId(); + const queuedRequest: QueuedRequest = { + id: requestId, + config, + resolve, + reject, + timestamp: Date.now(), + retryCount: 0, + }; + + this.requestQueue.push(queuedRequest); + this.processQueue(); + }); + } + + /** + * Process the request queue + */ + private async processQueue(): Promise { + if (this.isProcessingQueue || this.requestQueue.length === 0) { + return; + } + + this.isProcessingQueue = true; + + while (this.requestQueue.length > 0) { + const request = this.requestQueue.shift()!; + + try { + const host = this.extractHost(request.config.url || ''); + const currentConnections = this.activeRequests.get(host) || 0; + + // Check connection limits + if (currentConnections >= this.config.maxConnectionsPerHost) { + // Put request back in queue + this.requestQueue.unshift(request); + break; + } + + // Check global connection limit + const totalActive = Array.from(this.activeRequests.values()).reduce((sum, count) => sum + count, 0); + if (totalActive >= this.config.maxConnections) { + this.requestQueue.unshift(request); + break; + } + + // Execute the request + this.executeRequest(request, host); + + } catch (error) { + request.reject(error); + } + } + + this.isProcessingQueue = false; + } + + /** + * Execute a single request + */ + private async executeRequest(request: QueuedRequest, host: string): Promise { + const client = this.getClient(host); + + // Increment active connections + this.activeRequests.set(host, (this.activeRequests.get(host) || 0) + 1); + + try { + // Add metadata to track timing + if (!request.config.metadata) { + request.config.metadata = {}; + } + request.config.metadata.startTime = Date.now(); + + // Execute request using our client + const response = await client.request(request.config); + request.resolve(response.data); + + } catch (error: any) { + // No need to handle retries explicitly as the BunHttpClient handles them internally + request.reject(error); + + // Emit retry event for monitoring + if (error.retryCount) { + this.emit('retry', { + requestId: request.id, + retryCount: error.retryCount, + error + }); + } + } finally { + // Decrement active connections + this.activeRequests.set(host, Math.max(0, (this.activeRequests.get(host) || 0) - 1)); + } + } + + /** + * Extract host from URL + */ + private extractHost(url: string): string { + try { + const urlObj = new URL(url); + return urlObj.host; + } catch { + return 'default'; + } + } + + /** + * Generate unique request ID + */ + private generateRequestId(): string { + return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + /** + * Update statistics + */ + private updateStats(success: boolean, responseTime: number): void { + this.stats.requestCount++; + this.stats.totalResponseTime += responseTime; + + if (success) { + this.stats.successfulRequests++; + } else { + this.stats.failedRequests++; + } + } + + /** + * Get connection pool statistics + */ + getStats(): ConnectionStats { + const totalActive = Array.from(this.activeRequests.values()).reduce((sum, count) => sum + count, 0); + const averageResponseTime = this.stats.requestCount > 0 + ? this.stats.totalResponseTime / this.stats.requestCount + : 0; + const utilization = this.config.maxConnections > 0 + ? totalActive / this.config.maxConnections + : 0; + + // Combine our stats with the stats from all clients + const clientStats = Array.from(this.clients.values()).map(client => client.getStats()); + + let successfulRequests = this.stats.successfulRequests; + let failedRequests = this.stats.failedRequests; + + for (const stats of clientStats) { + successfulRequests += stats.successfulRequests; + failedRequests += stats.failedRequests; + } + + return { + activeConnections: totalActive, + totalConnections: this.stats.totalConnections, + successfulRequests, + failedRequests, + averageResponseTime, + connectionPoolUtilization: utilization, + requestsPerSecond: 0 // Will be calculated by the http-client + }; + } + + /** + * Start queue processor timer + */ + private startQueueProcessor(): void { + this.queueProcessor = setInterval(() => { + this.processQueue(); + }, 100); // Process queue every 100ms + } + + /** + * Close all connections and clean up + */ + async close(): Promise { + // Stop the queue processor + if (this.queueProcessor) { + clearInterval(this.queueProcessor); + } + + // Wait for pending requests to complete (with timeout) + const timeout = 30000; // 30 seconds + const startTime = Date.now(); + + while (this.requestQueue.length > 0 && Date.now() - startTime < timeout) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + + // Clear remaining requests + while (this.requestQueue.length > 0) { + const request = this.requestQueue.shift()!; + request.reject(new Error('Connection pool closing')); + } + + // Close all clients + const closePromises = Array.from(this.clients.values()).map(client => client.close()); + await Promise.all(closePromises); + + // Clear clients and requests + this.clients.clear(); + this.activeRequests.clear(); + + this.emit('closed'); + } + + /** + * Health check for the connection pool + */ + async healthCheck(): Promise<{ healthy: boolean; details: any }> { + const stats = this.getStats(); + const queueSize = this.requestQueue.length; + + // Check health of all clients + const clientHealthChecks = await Promise.all( + Array.from(this.clients.entries()).map(async ([host, client]) => { + const health = await client.healthCheck(); + return { + host, + healthy: health.healthy, + details: health.details + }; + }) + ); + + const healthy = + stats.connectionPoolUtilization < 0.9 && // Less than 90% utilization + queueSize < 100 && // Queue not too large + stats.averageResponseTime < 5000 && // Average response time under 5 seconds + clientHealthChecks.every(check => check.healthy); // All clients healthy + + return { + healthy, + details: { + stats, + queueSize, + clients: clientHealthChecks, + connections: Array.from(this.clients.keys()), + }, + }; + } +} diff --git a/apps/core-services/market-data-gateway/src/services/DataNormalizer.ts b/apps/core-services/market-data-gateway/src/services/DataNormalizer.ts index 60df311..2d7579a 100644 --- a/apps/core-services/market-data-gateway/src/services/DataNormalizer.ts +++ b/apps/core-services/market-data-gateway/src/services/DataNormalizer.ts @@ -1,36 +1,164 @@ -import type { MarketData, OHLCV } from '@stock-bot/shared-types'; -import { dataProviderConfigs } from '@stock-bot/config'; +import { dataProviderConfigs, DataProviderConfig } from '../config/DataProviderConfig'; + +// Define local types for market data +interface MarketDataType { + symbol: string; + price: number; + bid: number; + ask: number; + volume: number; + timestamp: Date; +} + +interface OHLCVType { + symbol: string; + timestamp: Date; + open: number; + high: number; + low: number; + close: number; + volume: number; +} + +export interface DataNormalizationResult { + success: boolean; + data?: T; + error?: string; + source: string; + timestamp: Date; + processingTimeMs: number; +} + +export interface DataQualityMetrics { + completeness: number; // 0-1 + accuracy: number; // 0-1 + timeliness: number; // 0-1 + consistency: number; // 0-1 + overall: number; // 0-1 +} export class DataNormalizer { + private readonly providerConfigs: Record; + + constructor() { + this.providerConfigs = dataProviderConfigs; + } + /** * Normalize market data from different providers to our standard format */ - normalizeMarketData(rawData: any, source: string): MarketData { - switch (source) { - case 'alpha-vantage': - return this.normalizeAlphaVantage(rawData); - case 'yahoo-finance': - return this.normalizeYahooFinance(rawData); - default: - throw new Error(`Unsupported data source: ${source}`); - } - } + normalizeMarketData(rawData: any, source: string): DataNormalizationResult { + const startTime = Date.now(); + try { + let normalizedData: MarketDataType; + + switch (source.toLowerCase()) { + case 'alpha-vantage': + normalizedData = this.normalizeAlphaVantage(rawData); + break; + case 'yahoo-finance': + normalizedData = this.normalizeYahooFinance(rawData); + break; + case 'polygon': + normalizedData = this.normalizePolygon(rawData); + break; + default: + return { + success: false, + error: `Unsupported data source: ${source}`, + source, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + }; + } - /** + // Validate the normalized data + if (!this.validateMarketData(normalizedData)) { + return { + success: false, + error: 'Data validation failed', + source, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + }; + } + + return { + success: true, + data: normalizedData, + source, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + source, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + }; + } + } /** * Normalize OHLCV data from different providers */ - normalizeOHLCV(rawData: any, source: string): OHLCV[] { - switch (source) { - case 'alpha-vantage': - return this.normalizeAlphaVantageOHLCV(rawData); - case 'yahoo-finance': - return this.normalizeYahooFinanceOHLCV(rawData); - default: - throw new Error(`Unsupported data source: ${source}`); + normalizeOHLCV(rawData: any, source: string): DataNormalizationResult { + const startTime = Date.now(); + try { + let normalizedData: OHLCVType[]; + + switch (source.toLowerCase()) { + case 'alpha-vantage': + normalizedData = this.normalizeAlphaVantageOHLCV(rawData); + break; + case 'yahoo-finance': + normalizedData = this.normalizeYahooFinanceOHLCV(rawData); + break; + case 'polygon': + normalizedData = this.normalizePolygonOHLCV(rawData); + break; + default: + return { + success: false, + error: `Unsupported data source: ${source}`, + source, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + }; + } + + // Validate each OHLCV entry + const validData = normalizedData.filter(item => this.validateOHLCV(item)); + + if (validData.length === 0) { + return { + success: false, + error: 'No valid OHLCV data after normalization', + source, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + }; + } + + return { + success: true, + data: validData, + source, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Unknown error', + source, + timestamp: new Date(), + processingTimeMs: Date.now() - startTime, + }; } } - private normalizeAlphaVantage(data: any): MarketData { + private normalizeAlphaVantage(data: any): MarketDataType { const quote = data['Global Quote']; return { symbol: quote['01. symbol'], @@ -41,8 +169,7 @@ export class DataNormalizer { timestamp: new Date(), }; } - - private normalizeYahooFinance(data: any): MarketData { + private normalizeYahooFinance(data: any): MarketDataType { return { symbol: data.symbol, price: data.regularMarketPrice, @@ -53,7 +180,19 @@ export class DataNormalizer { }; } - private normalizeAlphaVantageOHLCV(data: any): OHLCV[] { + private normalizePolygon(data: any): MarketDataType { + // Polygon.io format normalization + return { + symbol: data.T || data.symbol, + price: data.c || data.price, + bid: data.b || data.bid, + ask: data.a || data.ask, + volume: data.v || data.volume, + timestamp: new Date(data.t || data.timestamp), + }; + } + + private normalizeAlphaVantageOHLCV(data: any): OHLCVType[] { const timeSeries = data['Time Series (1min)'] || data['Time Series (5min)'] || data['Time Series (Daily)']; const symbol = data['Meta Data']['2. Symbol']; @@ -67,8 +206,7 @@ export class DataNormalizer { volume: parseInt(values['5. volume']), })).sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } - - private normalizeYahooFinanceOHLCV(data: any): OHLCV[] { + private normalizeYahooFinanceOHLCV(data: any): OHLCVType[] { const result = data.chart.result[0]; const timestamps = result.timestamp; const quotes = result.indicators.quote[0]; @@ -84,26 +222,48 @@ export class DataNormalizer { })); } - /** + private normalizePolygonOHLCV(data: any): OHLCVType[] { + // Polygon.io aggregates format + if (data.results && Array.isArray(data.results)) { + return data.results.map((candle: any) => ({ + symbol: data.ticker || candle.T, + timestamp: new Date(candle.t), + open: candle.o, + high: candle.h, + low: candle.l, + close: candle.c, + volume: candle.v, + })); + } + + return []; + } /** * Validate market data quality */ - validateMarketData(data: MarketData): boolean { + validateMarketData(data: MarketDataType): boolean { return ( data.symbol && + typeof data.symbol === 'string' && + data.symbol.length > 0 && typeof data.price === 'number' && data.price > 0 && typeof data.volume === 'number' && data.volume >= 0 && - data.timestamp instanceof Date + data.timestamp instanceof Date && + !isNaN(data.timestamp.getTime()) && + typeof data.bid === 'number' && + typeof data.ask === 'number' && + data.ask >= data.bid ) as boolean; } - /** * Validate OHLCV data quality */ - validateOHLCV(data: OHLCV): boolean { + validateOHLCV(data: OHLCVType): boolean { return ( data.symbol && + typeof data.symbol === 'string' && + data.symbol.length > 0 && typeof data.open === 'number' && data.open > 0 && typeof data.high === 'number' && data.high > 0 && typeof data.low === 'number' && data.low > 0 && @@ -111,7 +271,126 @@ export class DataNormalizer { data.high >= Math.max(data.open, data.close) && data.low <= Math.min(data.open, data.close) && typeof data.volume === 'number' && data.volume >= 0 && - data.timestamp instanceof Date + data.timestamp instanceof Date && + !isNaN(data.timestamp.getTime()) ) as boolean; } + /** + * Assess data quality metrics for market data + */ + assessDataQuality(data: MarketDataType[], source: string): DataQualityMetrics { + if (data.length === 0) { + return { + completeness: 0, + accuracy: 0, + timeliness: 0, + consistency: 0, + overall: 0, + }; + } + + // Completeness: percentage of valid data points + const validCount = data.filter(item => this.validateMarketData(item)).length; + const completeness = validCount / data.length; + + // Accuracy: based on price consistency and reasonable ranges + const accuracyScore = this.assessAccuracy(data); + + // Timeliness: based on data freshness + const timelinessScore = this.assessTimeliness(data); + + // Consistency: based on data patterns and outliers + const consistencyScore = this.assessConsistency(data); + + const overall = (completeness + accuracyScore + timelinessScore + consistencyScore) / 4; + + return { + completeness, + accuracy: accuracyScore, + timeliness: timelinessScore, + consistency: consistencyScore, + overall, + }; + } + + private assessAccuracy(data: MarketDataType[]): number { + let accuracySum = 0; + + for (const item of data) { + let score = 1.0; + + // Check for reasonable price ranges + if (item.price <= 0 || item.price > 100000) score -= 0.3; + + // Check bid/ask spread reasonableness + const spread = item.ask - item.bid; + const spreadPercentage = spread / item.price; + if (spreadPercentage > 0.1) score -= 0.2; // More than 10% spread is suspicious + + // Check for negative volume + if (item.volume < 0) score -= 0.5; + + accuracySum += Math.max(0, score); + } + + return data.length > 0 ? accuracySum / data.length : 0; + } + + private assessTimeliness(data: MarketDataType[]): number { + const now = new Date(); + let timelinessSum = 0; + + for (const item of data) { + const ageMs = now.getTime() - item.timestamp.getTime(); + const ageMinutes = ageMs / (1000 * 60); + + // Score based on data age (fresher is better) + let score = 1.0; + if (ageMinutes > 60) score = 0.1; // Very old data + else if (ageMinutes > 15) score = 0.5; // Moderately old + else if (ageMinutes > 5) score = 0.8; // Slightly old + + timelinessSum += score; + } + + return data.length > 0 ? timelinessSum / data.length : 0; + } + + private assessConsistency(data: MarketDataType[]): number { + if (data.length < 2) return 1.0; + + // Sort by timestamp + const sortedData = [...data].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + + let consistencySum = 0; + + for (let i = 1; i < sortedData.length; i++) { + const current = sortedData[i]; + const previous = sortedData[i - 1]; + + // Check for reasonable price movements + const priceChange = Math.abs(current.price - previous.price) / previous.price; + + let score = 1.0; + if (priceChange > 0.5) score -= 0.7; // More than 50% change is suspicious + else if (priceChange > 0.1) score -= 0.3; // More than 10% change is notable + + consistencySum += Math.max(0, score); + } + + return consistencySum / (sortedData.length - 1); + } + /** + * Clean and sanitize market data + */ + sanitizeMarketData(data: MarketDataType): MarketDataType { + return { + symbol: data.symbol.toUpperCase().trim(), + price: Math.max(0, Number(data.price) || 0), + bid: Math.max(0, Number(data.bid) || 0), + ask: Math.max(0, Number(data.ask) || 0), + volume: Math.max(0, Math.floor(Number(data.volume) || 0)), + timestamp: new Date(data.timestamp), + }; + } } diff --git a/apps/core-services/market-data-gateway/src/services/DataSourceManager.ts b/apps/core-services/market-data-gateway/src/services/DataSourceManager.ts index a9ecb23..438b47c 100644 --- a/apps/core-services/market-data-gateway/src/services/DataSourceManager.ts +++ b/apps/core-services/market-data-gateway/src/services/DataSourceManager.ts @@ -1,7 +1,43 @@ import { EventEmitter } from 'eventemitter3'; -import { Logger } from 'pino'; +// Local logger interface to avoid pino dependency issues +interface Logger { + info(msg: string, ...args: any[]): void; + error(msg: string, ...args: any[]): void; + warn(msg: string, ...args: any[]): void; + debug(msg: string, ...args: any[]): void; + child(options: any): Logger; +} + +// Simple logger implementation +const createLogger = (name: string): Logger => ({ + info: (msg: string, ...args: any[]) => console.log(`[${name}] INFO:`, msg, ...args), + error: (msg: string, ...args: any[]) => console.error(`[${name}] ERROR:`, msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn(`[${name}] WARN:`, msg, ...args), + debug: (msg: string, ...args: any[]) => console.debug(`[${name}] DEBUG:`, msg, ...args), + child: (options: any) => createLogger(`${name}.${options.component || 'child'}`) +}); + import WebSocket from 'ws'; -import axios, { AxiosInstance } from 'axios'; +// Simple HTTP client to replace axios +interface HttpClient { + get(url: string): Promise<{ data: any }>; + post(url: string, data?: any): Promise<{ data: any }>; +} + +const createHttpClient = (baseURL: string, headers?: Record): HttpClient => ({ + get: async (url: string) => { + const response = await fetch(`${baseURL}${url}`, { headers }); + return { data: await response.json() }; + }, + post: async (url: string, data?: any) => { + const response = await fetch(`${baseURL}${url}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', ...headers }, + body: data ? JSON.stringify(data) : undefined + }); + return { data: await response.json() }; + } +}); import { DataSourceConfig, DataSourceMetrics, @@ -13,7 +49,7 @@ import { interface DataSourceConnection { config: DataSourceConfig; - connection?: WebSocket | AxiosInstance; + connection?: WebSocket | HttpClient; status: 'disconnected' | 'connecting' | 'connected' | 'error'; lastConnectedAt?: Date; lastErrorAt?: Date; @@ -112,9 +148,8 @@ export class DataSourceManager extends EventEmitter { await this.connectDataSource(config.id); } } - public async removeDataSource(sourceId: string): Promise { - this.logger.info({ sourceId }, 'Removing data source'); + this.logger.info(`Removing data source: ${sourceId}`); await this.disconnectDataSource(sourceId); this.dataSources.delete(sourceId); @@ -132,7 +167,7 @@ export class DataSourceManager extends EventEmitter { throw new Error(`Data source ${sourceId} not found`); } - this.logger.info({ sourceId, updates }, 'Updating data source'); + this.logger.info(`Updating data source: ${sourceId}`, updates); // Update configuration dataSource.config = { ...dataSource.config, ...updates }; diff --git a/apps/core-services/market-data-gateway/src/services/EventPublisher.ts b/apps/core-services/market-data-gateway/src/services/EventPublisher.ts index 3bfc790..6c70cf8 100644 --- a/apps/core-services/market-data-gateway/src/services/EventPublisher.ts +++ b/apps/core-services/market-data-gateway/src/services/EventPublisher.ts @@ -4,12 +4,14 @@ import type { MarketDataEvent, SignalEvent, TradingEvent } from '@stock-bot/shar export class EventPublisher { private dragonfly: Redis; - private readonly STREAM_NAME = 'trading-events'; constructor() { + private readonly STREAM_NAME = 'trading-events'; + + constructor() { this.dragonfly = new Redis({ host: databaseConfig.dragonfly.host, port: databaseConfig.dragonfly.port, password: databaseConfig.dragonfly.password, - maxRetriesPerRequest: 3, + maxRetriesPerRequest: databaseConfig.dragonfly.maxRetriesPerRequest, }); this.dragonfly.on('connect', () => { diff --git a/apps/core-services/market-data-gateway/src/services/MarketDataGatewayService.ts b/apps/core-services/market-data-gateway/src/services/MarketDataGatewayService.ts index 10caf1c..122835c 100644 --- a/apps/core-services/market-data-gateway/src/services/MarketDataGatewayService.ts +++ b/apps/core-services/market-data-gateway/src/services/MarketDataGatewayService.ts @@ -1,5 +1,21 @@ import { EventEmitter } from 'eventemitter3'; -import { Logger } from 'pino'; +// Local logger interface to avoid pino dependency issues +interface Logger { + info(msg: string, ...args: any[]): void; + error(msg: string, ...args: any[]): void; + warn(msg: string, ...args: any[]): void; + debug(msg: string, ...args: any[]): void; + child(options: any): Logger; +} + +// Simple logger implementation +const createLogger = (name: string): Logger => ({ + info: (msg: string, ...args: any[]) => console.log(`[${name}] INFO:`, msg, ...args), + error: (msg: string, ...args: any[]) => console.error(`[${name}] ERROR:`, msg, ...args), + warn: (msg: string, ...args: any[]) => console.warn(`[${name}] WARN:`, msg, ...args), + debug: (msg: string, ...args: any[]) => console.debug(`[${name}] DEBUG:`, msg, ...args), + child: (options: any) => createLogger(`${name}.${options.component || 'child'}`) +}); import { GatewayConfig, DataSourceConfig, diff --git a/apps/data-services/market-data-gateway/README.md b/apps/data-services/market-data-gateway/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/package.json b/apps/data-services/market-data-gateway/package.json deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/controllers/GatewayController.ts b/apps/data-services/market-data-gateway/src/controllers/GatewayController.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/controllers/HealthController.ts b/apps/data-services/market-data-gateway/src/controllers/HealthController.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/controllers/MetricsController.ts b/apps/data-services/market-data-gateway/src/controllers/MetricsController.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/index.ts b/apps/data-services/market-data-gateway/src/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/services/CacheManager.ts b/apps/data-services/market-data-gateway/src/services/CacheManager.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/services/DataSourceManager.ts b/apps/data-services/market-data-gateway/src/services/DataSourceManager.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/services/MarketDataGatewayService.ts b/apps/data-services/market-data-gateway/src/services/MarketDataGatewayService.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/services/MetricsCollector.ts b/apps/data-services/market-data-gateway/src/services/MetricsCollector.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/services/ProcessingEngine.ts b/apps/data-services/market-data-gateway/src/services/ProcessingEngine.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/services/ServiceIntegrationManager.ts b/apps/data-services/market-data-gateway/src/services/ServiceIntegrationManager.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/services/SubscriptionManager.ts b/apps/data-services/market-data-gateway/src/services/SubscriptionManager.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/src/types/MarketDataGateway.ts b/apps/data-services/market-data-gateway/src/types/MarketDataGateway.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/data-services/market-data-gateway/tsconfig.json b/apps/data-services/market-data-gateway/tsconfig.json deleted file mode 100644 index e69de29..0000000 diff --git a/bun.lock b/bun.lock index 0a8865e..d5cc489 100644 --- a/bun.lock +++ b/bun.lock @@ -5,23 +5,46 @@ "name": "stock-bot", "devDependencies": { "@types/node": "^20.12.12", - "turbo": "^2.0.5", + "turbo": "^2.5.4", "typescript": "^5.4.5", }, }, "apps/core-services/market-data-gateway": { - "name": "market-data-gateway", + "name": "@stock-bot/market-data-gateway", "version": "1.0.0", "dependencies": { + "@hono/node-server": "^1.8.0", "@stock-bot/config": "*", + "@stock-bot/event-bus": "*", + "@stock-bot/http-client": "*", "@stock-bot/shared-types": "*", + "@stock-bot/utils": "*", + "bull": "^4.12.0", + "compression": "^1.7.4", + "dotenv": "^16.3.0", + "eventemitter3": "^5.0.1", + "fast-json-stringify": "^5.10.0", + "helmet": "^7.1.0", "hono": "^4.6.3", "ioredis": "^5.4.1", + "node-cron": "^3.0.3", + "pino": "^8.17.0", + "rate-limiter-flexible": "^5.0.0", + "uuid": "^9.0.0", "ws": "^8.18.0", + "zod": "^3.22.0", }, "devDependencies": { + "@types/compression": "^1.7.5", + "@types/node": "^20.11.0", + "@types/node-cron": "^3.0.11", + "@types/uuid": "^9.0.0", "@types/ws": "^8.5.12", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", "bun-types": "^1.2.15", + "eslint": "^8.56.0", + "typescript": "^5.3.0", }, }, "apps/core-services/risk-guardian": { @@ -39,12 +62,123 @@ "bun-types": "^1.2.15", }, }, + "apps/data-services/data-catalog": { + "name": "@stock-bot/data-catalog", + "version": "1.0.0", + "dependencies": { + "@stock-bot/api-client": "workspace:*", + "@stock-bot/event-bus": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "@stock-bot/utils": "workspace:*", + "cron": "^3.1.6", + "elasticsearch": "^16.7.3", + "hono": "^4.0.0", + "neo4j-driver": "^5.15.0", + "uuid": "^9.0.1", + "zod": "^3.22.0", + }, + "devDependencies": { + "@types/cron": "^2.4.0", + "@types/node": "^20.0.0", + "@types/uuid": "^9.0.8", + "typescript": "^5.3.0", + }, + }, + "apps/data-services/data-processor": { + "name": "data-processor", + "version": "1.0.0", + "dependencies": { + "@stock-bot/api-client": "*", + "@stock-bot/event-bus": "*", + "@stock-bot/shared-types": "*", + "@stock-bot/utils": "*", + "axios": "^1.6.2", + "bull": "^4.12.2", + "cron": "^3.1.6", + "csv-parser": "^3.0.0", + "hono": "^4.6.3", + "ioredis": "^5.4.1", + "joi": "^17.11.0", + "node-fetch": "^3.3.2", + }, + "devDependencies": { + "@types/bull": "^4.10.0", + "@types/node": "^20.10.5", + "bun-types": "^1.2.15", + "eslint": "^8.56.0", + "typescript": "^5.3.3", + }, + }, + "apps/data-services/feature-store": { + "name": "feature-store", + "version": "1.0.0", + "dependencies": { + "@stock-bot/api-client": "*", + "@stock-bot/event-bus": "*", + "@stock-bot/shared-types": "*", + "@stock-bot/utils": "*", + "compression": "^1.7.4", + "cors": "^2.8.5", + "date-fns": "^2.30.0", + "helmet": "^7.1.0", + "hono": "^4.6.3", + "ioredis": "^5.4.1", + "lodash": "^4.17.21", + "node-fetch": "^3.3.2", + }, + "devDependencies": { + "@types/bun": "latest", + "@types/compression": "^1.7.5", + "@types/cors": "^2.8.17", + "@types/lodash": "^4.14.200", + "@typescript-eslint/eslint-plugin": "^6.13.1", + "@typescript-eslint/parser": "^6.13.1", + "eslint": "^8.55.0", + "typescript": "^5.3.0", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, + "apps/intelligence-services/backtest-engine": { + "name": "backtest-engine", + "version": "1.0.0", + "dependencies": { + "@stock-bot/api-client": "workspace:*", + "@stock-bot/config": "*", + "@stock-bot/event-bus": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "@stock-bot/utils": "workspace:*", + "axios": "^1.6.2", + "hono": "^4.6.3", + "ws": "^8.18.0", + }, + "devDependencies": { + "@types/ws": "^8.5.12", + "bun-types": "^1.2.15", + }, + }, + "apps/intelligence-services/signal-engine": { + "name": "signal-engine", + "version": "1.0.0", + "dependencies": { + "@stock-bot/config": "*", + "@stock-bot/shared-types": "*", + "hono": "^4.6.3", + "ws": "^8.18.0", + }, + "devDependencies": { + "@types/ws": "^8.5.12", + "bun-types": "^1.2.15", + }, + }, "apps/intelligence-services/strategy-orchestrator": { "name": "strategy-orchestrator", "version": "1.0.0", "dependencies": { "@stock-bot/config": "*", "@stock-bot/shared-types": "*", + "axios": "^1.6.2", "hono": "^4.6.3", "ioredis": "^5.4.1", "node-cron": "^3.0.3", @@ -91,25 +225,84 @@ "typescript": "~5.8.3", }, }, - "packages/config": { - "name": "@stock-bot/config", + "libs/api-client": { + "name": "@stock-bot/api-client", "version": "1.0.0", "dependencies": { - "@stock-bot/shared-types": "*", - "dotenv": "^16.4.5", + "@stock-bot/shared-types": "workspace:*", + "axios": "^1.6.0", }, "devDependencies": { - "@types/node": "^20.12.12", + "@types/jest": "^29.5.2", + "jest": "^29.5.0", "typescript": "^5.4.5", }, }, - "packages/shared-types": { + "libs/config": { + "name": "@stock-bot/config", + "version": "1.0.0", + "dependencies": { + "dotenv": "^16.3.1", + "zod": "^3.22.4", + }, + "devDependencies": { + "@types/node": "^20.11.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "bun-types": "^1.2.15", + "eslint": "^8.56.0", + "typescript": "^5.3.0", + }, + }, + "libs/event-bus": { + "name": "@stock-bot/event-bus", + "version": "1.0.0", + "dependencies": { + "@stock-bot/shared-types": "workspace:*", + "ioredis": "^5.3.2", + }, + "devDependencies": { + "@types/jest": "^29.5.2", + "jest": "^29.5.0", + "typescript": "^5.4.5", + }, + }, + "libs/http-client": { + "name": "@stock-bot/http-client", + "version": "1.0.0", + "dependencies": { + "eventemitter3": "^5.0.1", + }, + "devDependencies": { + "@types/node": "^20.11.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "bun-types": "^1.2.15", + "eslint": "^8.56.0", + "typescript": "^5.3.0", + }, + }, + "libs/shared-types": { "name": "@stock-bot/shared-types", "version": "1.0.0", "devDependencies": { "typescript": "^5.4.5", }, }, + "libs/utils": { + "name": "@stock-bot/utils", + "version": "1.0.0", + "dependencies": { + "@stock-bot/config": "workspace:*", + "@stock-bot/shared-types": "workspace:*", + "date-fns": "^2.30.0", + }, + "devDependencies": { + "@types/jest": "^29.5.2", + "jest": "^29.5.0", + "typescript": "^5.4.5", + }, + }, }, "packages": { "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -162,6 +355,8 @@ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="], + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], + "@babel/helper-split-export-declaration": ["@babel/helper-split-export-declaration@7.24.7", "", { "dependencies": { "@babel/types": "^7.24.7" } }, "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA=="], "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], @@ -174,12 +369,50 @@ "@babel/parser": ["@babel/parser@7.27.4", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g=="], + "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], + + "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="], + + "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="], + + "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="], + + "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], + + "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="], + + "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="], + + "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="], + + "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="], + + "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="], + + "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="], + + "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="], + + "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="], + + "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="], + + "@babel/runtime": ["@babel/runtime@7.27.4", "", {}, "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA=="], + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], "@babel/traverse": ["@babel/traverse@7.27.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.3", "@babel/parser": "^7.27.4", "@babel/template": "^7.27.2", "@babel/types": "^7.27.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA=="], "@babel/types": ["@babel/types@7.27.3", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw=="], + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], + "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="], @@ -232,6 +465,28 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], + + "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + + "@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.1.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3" } }, "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA=="], + + "@hapi/hoek": ["@hapi/hoek@9.3.0", "", {}, "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="], + + "@hapi/topo": ["@hapi/topo@5.1.0", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg=="], + + "@hono/node-server": ["@hono/node-server@1.14.3", "", { "peerDependencies": { "hono": "^4" } }, "sha512-KuDMwwghtFYSmIpr4WrKs1VpelTrptvJ+6x6mbUcZnFcc213cumTF5BdqfHyW93B19TNI4Vaev14vOI2a0Ie3w=="], + + "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + "@inquirer/checkbox": ["@inquirer/checkbox@4.1.8", "", { "dependencies": { "@inquirer/core": "^10.1.13", "@inquirer/figures": "^1.0.12", "@inquirer/type": "^3.0.7", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg=="], "@inquirer/confirm": ["@inquirer/confirm@5.1.10", "", { "dependencies": { "@inquirer/core": "^10.1.11", "@inquirer/type": "^3.0.6" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-FxbQ9giWxUWKUk2O5XZ6PduVnH2CZ/fmMKMBkH71MHJvWr7WL5AHKevhzF1L5uYWB2P548o1RzVxrNd3dpmk6g=="], @@ -266,8 +521,38 @@ "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], + "@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="], + + "@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="], + + "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="], + + "@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="], + + "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="], + + "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="], + + "@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="], + + "@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="], + + "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], + + "@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="], + + "@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="], + + "@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="], + + "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="], + + "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="], "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], @@ -340,6 +625,12 @@ "@napi-rs/nice-win32-x64-msvc": ["@napi-rs/nice-win32-x64-msvc@1.0.1", "", { "os": "win32", "cpu": "x64" }, "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="], "@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="], @@ -430,6 +721,12 @@ "@schematics/angular": ["@schematics/angular@20.0.0", "", { "dependencies": { "@angular-devkit/core": "20.0.0", "@angular-devkit/schematics": "20.0.0", "jsonc-parser": "3.3.1" } }, "sha512-lK5TvxEoeaoPnxM31qeNWhHUJ3kKMnRHknYhOfOmS8xfme78nS01FdU7TODLkg2p4GNEVVtXoxhj3FmrG3srKw=="], + "@sideway/address": ["@sideway/address@4.1.5", "", { "dependencies": { "@hapi/hoek": "^9.0.0" } }, "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q=="], + + "@sideway/formula": ["@sideway/formula@3.0.1", "", {}, "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="], + + "@sideway/pinpoint": ["@sideway/pinpoint@2.0.0", "", {}, "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="], + "@sigstore/bundle": ["@sigstore/bundle@3.1.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.4.0" } }, "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag=="], "@sigstore/core": ["@sigstore/core@2.0.0", "", {}, "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg=="], @@ -442,11 +739,29 @@ "@sigstore/verify": ["@sigstore/verify@2.1.1", "", { "dependencies": { "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", "@sigstore/protobuf-specs": "^0.4.1" } }, "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w=="], + "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="], + + "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], + + "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="], + "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], - "@stock-bot/config": ["@stock-bot/config@workspace:packages/config"], + "@stock-bot/api-client": ["@stock-bot/api-client@workspace:libs/api-client"], - "@stock-bot/shared-types": ["@stock-bot/shared-types@workspace:packages/shared-types"], + "@stock-bot/config": ["@stock-bot/config@workspace:libs/config"], + + "@stock-bot/data-catalog": ["@stock-bot/data-catalog@workspace:apps/data-services/data-catalog"], + + "@stock-bot/event-bus": ["@stock-bot/event-bus@workspace:libs/event-bus"], + + "@stock-bot/http-client": ["@stock-bot/http-client@workspace:libs/http-client"], + + "@stock-bot/market-data-gateway": ["@stock-bot/market-data-gateway@workspace:apps/core-services/market-data-gateway"], + + "@stock-bot/shared-types": ["@stock-bot/shared-types@workspace:libs/shared-types"], + + "@stock-bot/utils": ["@stock-bot/utils@workspace:libs/utils"], "@tailwindcss/node": ["@tailwindcss/node@4.1.8", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.8" } }, "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q=="], @@ -482,44 +797,156 @@ "@tufjs/models": ["@tufjs/models@3.0.1", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.5" } }, "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA=="], + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="], + + "@types/body-parser": ["@types/body-parser@1.19.5", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg=="], + + "@types/bull": ["@types/bull@4.10.4", "", { "dependencies": { "bull": "*" } }, "sha512-A+8uxa5GbzKcS7kZ9Z1OcOeSyrvVmfHtZi3VIrH1Gws0G0sTknB2SRllxTaAYhycGn7+nC0Pb8VjxIyZiTM81A=="], + + "@types/bun": ["@types/bun@1.2.15", "", { "dependencies": { "bun-types": "1.2.15" } }, "sha512-U1ljPdBEphF0nw1MIk0hI7kPg7dFdPyM7EenHsp6W5loNHl7zqy6JQf/RKCgnUn2KDzUpkBwHPnEJEjII594bA=="], + + "@types/compression": ["@types/compression@1.8.0", "", { "dependencies": { "@types/express": "*", "@types/node": "*" } }, "sha512-g4vmPIwbTii9dX1HVioHbOolubEaf4re4vDxuzpKrzz9uI7uarBExi9begX0cXyIB85jXZ5X2A/v8rsHZxSAPw=="], + + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], + "@types/cors": ["@types/cors@2.8.18", "", { "dependencies": { "@types/node": "*" } }, "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA=="], + "@types/cron": ["@types/cron@2.4.3", "", { "dependencies": { "cron": "*" } }, "sha512-ViRBkoZD9Rk0hGeMdd2GHGaOaZuH9mDmwsE5/Zo53Ftwcvh7h9VJc8lIt2wdgEwS4EW5lbtTX6vlE0idCLPOyA=="], + "@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="], + "@types/express": ["@types/express@5.0.2", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "*" } }, "sha512-BtjL3ZwbCQriyb0DGw+Rt12qAXPiBTPs815lsUvtt1Grk0vLRMZNMUZ741d5rjk+UQOxfDiBZ3dxpX00vSkK3g=="], + + "@types/express-serve-static-core": ["@types/express-serve-static-core@5.0.6", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA=="], + + "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="], + + "@types/http-errors": ["@types/http-errors@2.0.4", "", {}, "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA=="], + + "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], + + "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], + + "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], + "@types/jasmine": ["@types/jasmine@5.1.8", "", {}, "sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg=="], + "@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/lodash": ["@types/lodash@4.17.17", "", {}, "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ=="], + + "@types/luxon": ["@types/luxon@3.4.2", "", {}, "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA=="], + + "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], + "@types/node": ["@types/node@20.17.57", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ=="], "@types/node-cron": ["@types/node-cron@3.0.11", "", {}, "sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg=="], + "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], + + "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], + + "@types/semver": ["@types/semver@7.7.0", "", {}, "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA=="], + + "@types/send": ["@types/send@0.17.4", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA=="], + + "@types/serve-static": ["@types/serve-static@1.15.7", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "*" } }, "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw=="], + + "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], + + "@types/uuid": ["@types/uuid@9.0.8", "", {}, "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA=="], + "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], + "@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="], + + "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@6.21.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.5.1", "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/type-utils": "6.21.0", "@typescript-eslint/utils": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", "natural-compare": "^1.4.0", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@6.21.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0" } }, "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@6.21.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "6.21.0", "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@6.21.0", "", {}, "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "9.0.3", "semver": "^7.5.4", "ts-api-utils": "^1.0.1" } }, "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@6.21.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@6.21.0", "", { "dependencies": { "@typescript-eslint/types": "6.21.0", "eslint-visitor-keys": "^3.4.1" } }, "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + "@vitejs/plugin-basic-ssl": ["@vitejs/plugin-basic-ssl@2.0.0", "", { "peerDependencies": { "vite": "^6.0.0" } }, "sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA=="], "@yarnpkg/lockfile": ["@yarnpkg/lockfile@1.1.0", "", {}, "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ=="], "abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.3", "", {}, "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw=="], - "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "agentkeepalive": ["agentkeepalive@3.5.3", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-yqXL+k5rr8+ZRpOAntkaaRgWgE5o8ESAj5DyRmVTCSoZxXmqemb9Dd7T4i5UzwuERdLAJUy6XzR9zFVuf0kzkw=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], - "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + "autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="], + "axios": ["axios@1.9.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } }, "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg=="], + + "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="], + + "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="], + + "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="], + + "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.1.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw=="], + + "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="], + + "backtest-engine": ["backtest-engine@workspace:apps/intelligence-services/backtest-engine"], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], "beasties": ["beasties@0.3.4", "", { "dependencies": { "css-select": "^5.1.0", "css-what": "^6.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "htmlparser2": "^10.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.49", "postcss-media-query-parser": "^0.2.3" } }, "sha512-NmzN1zN1cvGccXFyZ73335+ASXwBlVWcUPssiUDIlFdfyatHPRRufjCd5w8oPaQPvVnf9ELklaCGb1gi9FBwIw=="], @@ -536,8 +963,14 @@ "browserslist": ["browserslist@4.25.0", "", { "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA=="], + "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], + + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bull": ["bull@4.16.5", "", { "dependencies": { "cron-parser": "^4.9.0", "get-port": "^5.1.1", "ioredis": "^5.3.2", "lodash": "^4.17.21", "msgpackr": "^1.11.2", "semver": "^7.5.2", "uuid": "^8.3.0" } }, "sha512-lDsx2BzkKe7gkCYiT5Acj02DpTwDznl/VNN7Psn7M3USPG7Vs/BaClZJJTAG+ufAR9++N1/NiUTdaFBWDIl5TQ=="], + "bun-types": ["bun-types@1.2.15", "", { "dependencies": { "@types/node": "*" } }, "sha512-NarRIaS+iOaQU1JPfyKhZm4AsUOrwUOqRNHY0XxI8GI8jYxiLXLcdjYMG9UKS+fwWasc1uw1htV9AX24dD+p4w=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -548,9 +981,15 @@ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001720", "", {}, "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g=="], - "chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], "chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="], @@ -558,6 +997,10 @@ "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], @@ -570,12 +1013,22 @@ "cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="], + "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], + + "collect-v8-coverage": ["collect-v8-coverage@1.0.2", "", {}, "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="], + + "compression": ["compression@1.8.0", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.0.2", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="], @@ -588,18 +1041,40 @@ "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], + "create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="], + + "cron": ["cron@3.5.0", "", { "dependencies": { "@types/luxon": "~3.4.0", "luxon": "~3.5.0" } }, "sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A=="], + + "cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "css-select": ["css-select@5.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg=="], "css-what": ["css-what@6.1.0", "", {}, "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="], + "csv-parser": ["csv-parser@3.2.0", "", { "bin": { "csv-parser": "bin/csv-parser" } }, "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA=="], + "custom-event": ["custom-event@1.0.1", "", {}, "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg=="], + "data-processor": ["data-processor@workspace:apps/data-services/data-processor"], + + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + + "date-fns": ["date-fns@2.30.0", "", { "dependencies": { "@babel/runtime": "^7.21.0" } }, "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw=="], + "date-format": ["date-format@4.0.14", "", {}, "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg=="], "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + "dedent": ["dedent@1.6.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -608,8 +1083,16 @@ "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], + "di": ["di@0.0.1", "", {}, "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA=="], + "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + "dom-serialize": ["dom-serialize@2.2.1", "", { "dependencies": { "custom-event": "~1.0.0", "ent": "~2.2.0", "extend": "^3.0.0", "void-elements": "^2.0.0" } }, "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ=="], "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], @@ -628,8 +1111,12 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "elasticsearch": ["elasticsearch@16.7.3", "", { "dependencies": { "agentkeepalive": "^3.4.1", "chalk": "^1.0.0", "lodash": "^4.17.10" } }, "sha512-e9kUNhwnIlu47fGAr4W6yZJbkpsgQJB0TqNK8rCANe1J4P65B1sGnbCFTgcKY3/dRgCWnuP1AJ4obvzW604xEQ=="], + "electron-to-chromium": ["electron-to-chromium@1.5.162", "", {}, "sha512-hQA+Zb5QQwoSaXJWEAGEw1zhk//O7qDzib05Z4qTqZfNju/FAkrm5ZInp0JbTp4Z18A6bilopdZWEYrFSsfllA=="], + "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], @@ -652,20 +1139,54 @@ "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="], "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], + + "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="], + + "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], + "exponential-backoff": ["exponential-backoff@3.1.2", "", {}, "sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], @@ -674,20 +1195,48 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - "fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-json-stringify": ["fast-json-stringify@5.16.1", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.1.0", "ajv": "^8.10.0", "ajv-formats": "^3.0.1", "fast-deep-equal": "^3.1.3", "fast-uri": "^2.1.0", "json-schema-ref-resolver": "^1.0.1", "rfdc": "^1.2.0" } }, "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], + + "fast-uri": ["fast-uri@2.4.0", "", {}, "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], "fdir": ["fdir@6.4.5", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw=="], + "feature-store": ["feature-store@workspace:apps/data-services/feature-store"], + + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@1.1.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], "follow-redirects": ["follow-redirects@1.15.9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "form-data": ["form-data@4.0.2", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "mime-types": "^2.1.12" } }, "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w=="], + + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + "fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], "fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], @@ -708,20 +1257,32 @@ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="], + + "get-port": ["get-port@5.1.1", "", {}, "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ=="], + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], - "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + "globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-ansi": ["has-ansi@2.0.0", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], @@ -730,6 +1291,8 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "helmet": ["helmet@7.2.0", "", {}, "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw=="], + "hono": ["hono@4.7.11", "", {}, "sha512-rv0JMwC0KALbbmwJDEnxvQCeJh+xbS3KEWW5PC9cMJ08Ur9xgatI0HmtgYZfOdOSOeYsp5LO2cOhdI8cLEbDEQ=="], "hosted-git-info": ["hosted-git-info@8.1.0", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw=="], @@ -748,12 +1311,24 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "ignore-walk": ["ignore-walk@7.0.0", "", { "dependencies": { "minimatch": "^9.0.0" } }, "sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ=="], "immutable": ["immutable@5.1.2", "", {}, "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ=="], + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], @@ -766,6 +1341,8 @@ "ip-address": ["ip-address@9.0.5", "", { "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" } }, "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g=="], + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], @@ -774,14 +1351,20 @@ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], "isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], @@ -802,17 +1385,79 @@ "jasmine-core": ["jasmine-core@5.7.1", "", {}, "sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw=="], + "jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="], + + "jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="], + + "jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="], + + "jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="], + + "jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="], + + "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="], + + "jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="], + + "jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="], + + "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="], + + "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="], + + "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="], + + "jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="], + + "jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="], + + "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="], + + "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="], + + "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="], + + "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="], + + "jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="], + + "jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="], + + "jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="], + + "jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="], + + "jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="], + + "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], + + "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="], + + "jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="], + + "jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="], + "jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="], + "joi": ["joi@17.13.3", "", { "dependencies": { "@hapi/hoek": "^9.3.0", "@hapi/topo": "^5.1.0", "@sideway/address": "^4.1.5", "@sideway/formula": "^3.0.1", "@sideway/pinpoint": "^2.0.0" } }, "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + "jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-parse-even-better-errors": ["json-parse-even-better-errors@4.0.0", "", {}, "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA=="], - "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "json-schema-ref-resolver": ["json-schema-ref-resolver@1.0.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3" } }, "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], @@ -832,6 +1477,14 @@ "karma-jasmine-html-reporter": ["karma-jasmine-html-reporter@2.1.0", "", { "peerDependencies": { "jasmine-core": "^4.0.0 || ^5.0.0", "karma": "^6.0.0", "karma-jasmine": "^5.0.0" } }, "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], + + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], @@ -854,16 +1507,22 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "listr2": ["listr2@8.3.3", "", { "dependencies": { "cli-truncate": "^4.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ=="], "lmdb": ["lmdb@3.3.0", "", { "dependencies": { "msgpackr": "^1.11.2", "node-addon-api": "^6.1.0", "node-gyp-build-optional-packages": "5.2.2", "ordered-binary": "^1.5.3", "weak-lru-cache": "^1.2.2" }, "optionalDependencies": { "@lmdb/lmdb-darwin-arm64": "3.3.0", "@lmdb/lmdb-darwin-x64": "3.3.0", "@lmdb/lmdb-linux-arm": "3.3.0", "@lmdb/lmdb-linux-arm64": "3.3.0", "@lmdb/lmdb-linux-x64": "3.3.0", "@lmdb/lmdb-win32-arm64": "3.3.0", "@lmdb/lmdb-win32-x64": "3.3.0" }, "bin": { "download-lmdb-prebuilds": "bin/download-prebuilds.js" } }, "sha512-MgJocUI6QEiSXQBFWLeyo1R7eQj8Rke5dlPxX0KFwli8/bsCxpM/KbXO5y0qmV/5llQ3wpneDWcTYxa+4vn8iQ=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], "lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], @@ -872,18 +1531,24 @@ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "luxon": ["luxon@3.5.0", "", {}, "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ=="], + "magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="], "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], "make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="], - "market-data-gateway": ["market-data-gateway@workspace:apps/core-services/market-data-gateway"], + "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], @@ -892,6 +1557,8 @@ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], @@ -926,16 +1593,30 @@ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + + "neo4j-driver": ["neo4j-driver@5.28.1", "", { "dependencies": { "neo4j-driver-bolt-connection": "5.28.1", "neo4j-driver-core": "5.28.1", "rxjs": "^7.8.1" } }, "sha512-jbyBwyM0a3RLGcP43q3hIxPUPxA+1bE04RovOKdNAS42EtBMVCKcPSeOvWiHxgXp1ZFd0a8XqK+7LtguInOLUg=="], + + "neo4j-driver-bolt-connection": ["neo4j-driver-bolt-connection@5.28.1", "", { "dependencies": { "buffer": "^6.0.3", "neo4j-driver-core": "5.28.1", "string_decoder": "^1.3.0" } }, "sha512-nY8GBhjOW7J0rDtpiyJn6kFdk2OiNVZZhZrO8//mwNXnf5VQJ6HqZQTDthH/9pEaX0Jvbastz1xU7ZL8xzqY0w=="], + + "neo4j-driver-core": ["neo4j-driver-core@5.28.1", "", {}, "sha512-14vN8TlxC0JvJYfjWic5PwjsZ38loQLOKFTXwk4fWLTbCk6VhrhubB2Jsy9Rz+gM6PtTor4+6ClBEFDp1q/c8g=="], "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], "node-cron": ["node-cron@3.0.3", "", { "dependencies": { "uuid": "8.3.2" } }, "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], + + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], + "node-gyp": ["node-gyp@11.2.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-T0S1zqskVUSxcsSTkAsLc7xCycrRYmtDHadDinzocrThjyQCn5kMlEBSj6H4qDbgsIOSLmmlRIeb0lZXj+UArA=="], "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], + "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], "nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="], @@ -958,17 +1639,25 @@ "npm-registry-fetch": ["npm-registry-fetch@18.0.2", "", { "dependencies": { "@npmcli/redact": "^3.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^14.0.0", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^12.0.0", "proc-log": "^5.0.0" } }, "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ=="], + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + "on-headers": ["on-headers@1.0.2", "", {}, "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], @@ -976,12 +1665,22 @@ "os-tmpdir": ["os-tmpdir@1.0.2", "", {}, "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "p-map": ["p-map@7.0.3", "", {}, "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA=="], + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], "pacote": ["pacote@21.0.0", "", { "dependencies": { "@npmcli/git": "^6.0.0", "@npmcli/installed-package-contents": "^3.0.0", "@npmcli/package-json": "^6.0.0", "@npmcli/promise-spawn": "^8.0.0", "@npmcli/run-script": "^9.0.0", "cacache": "^19.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^12.0.0", "npm-packlist": "^10.0.0", "npm-pick-manifest": "^10.0.0", "npm-registry-fetch": "^18.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "sigstore": "^3.0.0", "ssri": "^12.0.0", "tar": "^6.1.11" }, "bin": { "pacote": "bin/index.js" } }, "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA=="], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], "parse5-html-rewriting-stream": ["parse5-html-rewriting-stream@7.1.0", "", { "dependencies": { "entities": "^6.0.0", "parse5": "^7.0.0", "parse5-sax-parser": "^7.0.0" } }, "sha512-2ifK6Jb+ONoqOy5f+cYHsqvx1obHQdvIk13Jmt/5ezxP0U9p+fqd+R6O73KblGswyuzBYfetmsfK9ThMgnuPPg=="], @@ -990,6 +1689,8 @@ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -998,34 +1699,72 @@ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], + "pino": ["pino@8.21.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^1.2.0", "pino-std-serializers": "^6.0.0", "process-warning": "^3.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^3.7.0", "thread-stream": "^2.6.0" }, "bin": { "pino": "bin.js" } }, "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q=="], + + "pino-abstract-transport": ["pino-abstract-transport@1.2.0", "", { "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q=="], + + "pino-std-serializers": ["pino-std-serializers@6.2.2", "", {}, "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + "piscina": ["piscina@5.0.0", "", { "optionalDependencies": { "@napi-rs/nice": "^1.0.1" } }, "sha512-R+arufwL7sZvGjAhSMK3TfH55YdGOqhpKXkcwQJr432AAnJX/xxX19PA4QisrmJ+BTTfZVggaz6HexbkQq1l1Q=="], + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], + "postcss": ["postcss@8.5.4", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w=="], "postcss-media-query-parser": ["postcss-media-query-parser@0.2.3", "", {}, "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig=="], "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], + "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="], + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], + + "process-warning": ["process-warning@3.0.0", "", {}, "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="], + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], - "punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], "qjobs": ["qjobs@1.2.0", "", {}, "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg=="], "qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + "rate-limiter-flexible": ["rate-limiter-flexible@5.0.5", "", {}, "sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ=="], + "raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="], + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], "redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="], @@ -1040,10 +1779,18 @@ "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], + "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="], + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], @@ -1052,10 +1799,16 @@ "rollup": ["rollup@4.40.2", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.40.2", "@rollup/rollup-android-arm64": "4.40.2", "@rollup/rollup-darwin-arm64": "4.40.2", "@rollup/rollup-darwin-x64": "4.40.2", "@rollup/rollup-freebsd-arm64": "4.40.2", "@rollup/rollup-freebsd-x64": "4.40.2", "@rollup/rollup-linux-arm-gnueabihf": "4.40.2", "@rollup/rollup-linux-arm-musleabihf": "4.40.2", "@rollup/rollup-linux-arm64-gnu": "4.40.2", "@rollup/rollup-linux-arm64-musl": "4.40.2", "@rollup/rollup-linux-loongarch64-gnu": "4.40.2", "@rollup/rollup-linux-powerpc64le-gnu": "4.40.2", "@rollup/rollup-linux-riscv64-gnu": "4.40.2", "@rollup/rollup-linux-riscv64-musl": "4.40.2", "@rollup/rollup-linux-s390x-gnu": "4.40.2", "@rollup/rollup-linux-x64-gnu": "4.40.2", "@rollup/rollup-linux-x64-musl": "4.40.2", "@rollup/rollup-win32-arm64-msvc": "4.40.2", "@rollup/rollup-win32-ia32-msvc": "4.40.2", "@rollup/rollup-win32-x64-msvc": "4.40.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg=="], + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "sass": ["sass@1.88.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg=="], @@ -1076,10 +1829,16 @@ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "signal-engine": ["signal-engine@workspace:apps/intelligence-services/signal-engine"], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "sigstore": ["sigstore@3.1.0", "", { "dependencies": { "@sigstore/bundle": "^3.1.0", "@sigstore/core": "^2.0.0", "@sigstore/protobuf-specs": "^0.4.0", "@sigstore/sign": "^3.1.0", "@sigstore/tuf": "^3.1.0", "@sigstore/verify": "^2.1.0" } }, "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + "slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], @@ -1094,6 +1853,8 @@ "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "sonic-boom": ["sonic-boom@3.8.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -1108,10 +1869,14 @@ "spdx-license-ids": ["spdx-license-ids@3.0.21", "", {}, "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg=="], - "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], "ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="], + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + "standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="], "statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], @@ -1122,14 +1887,24 @@ "streamroller": ["streamroller@3.1.5", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "fs-extra": "^8.1.0" } }, "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw=="], + "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], + + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], @@ -1140,16 +1915,26 @@ "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="], + "tinyglobby": ["tinyglobby@0.2.13", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw=="], "tmp": ["tmp@0.2.3", "", {}, "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w=="], + "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "trading-dashboard": ["trading-dashboard@workspace:apps/interface-services/trading-dashboard"], + "ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tuf-js": ["tuf-js@3.0.1", "", { "dependencies": { "@tufjs/models": "3.0.1", "debug": "^4.3.6", "make-fetch-happen": "^14.0.1" } }, "sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA=="], @@ -1168,7 +1953,11 @@ "turbo-windows-arm64": ["turbo-windows-arm64@2.5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-oQ8RrK1VS8lrxkLriotFq+PiF7iiGgkZtfLKF4DDKsmdbPo0O9R2mQxm7jHLuXraRCuIQDWMIw6dpcr7Iykf4A=="], - "type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], + + "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], @@ -1188,9 +1977,13 @@ "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], - "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + + "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], @@ -1202,18 +1995,26 @@ "void-elements": ["void-elements@2.0.1", "", {}, "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung=="], + "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], + "watchpack": ["watchpack@2.4.2", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw=="], "weak-lru-cache": ["weak-lru-cache@1.2.2", "", {}, "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw=="], + "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="], + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="], + "ws": ["ws@8.18.2", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ=="], "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], @@ -1224,10 +2025,16 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "yoctocolors-cjs": ["yoctocolors-cjs@2.1.2", "", {}, "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA=="], + "zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="], + "zone.js": ["zone.js@0.15.1", "", {}, "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w=="], + "@angular-devkit/core/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "@angular-devkit/core/source-map": ["source-map@0.7.4", "", {}, "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA=="], "@babel/core/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], @@ -1236,12 +2043,24 @@ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], + "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], + + "@jest/transform/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + "@listr2/prompt-adapter-inquirer/@inquirer/type": ["@inquirer/type@1.5.5", "", { "dependencies": { "mute-stream": "^1.0.0" } }, "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA=="], "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -1276,48 +2095,84 @@ "@tufjs/models/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.3", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg=="], + + "accepts/negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], + + "ajv-formats/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + + "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], + "body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "bull/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "cacache/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "cacache/tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], - "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "elasticsearch/chalk": ["chalk@1.1.3", "", { "dependencies": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", "has-ansi": "^2.0.0", "strip-ansi": "^3.0.0", "supports-color": "^2.0.0" } }, "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A=="], + "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "engine.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], "engine.io/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="], + "ent/punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], + + "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "external-editor/tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "fast-json-stringify/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], + "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "finalhandler/on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="], "finalhandler/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="], + "has-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + "hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "http-proxy/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], "ignore-walk/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "ip-address/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + + "jest-runner/source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="], + + "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + "karma/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "karma/yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="], @@ -1326,12 +2181,16 @@ "karma-jasmine/jasmine-core": ["jasmine-core@4.6.1", "", {}, "sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ=="], + "log-symbols/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], "log-update/ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="], "log-update/slice-ansi": ["slice-ansi@7.1.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg=="], + "log-update/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -1344,14 +2203,28 @@ "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "node-cron/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "node-gyp/tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], "node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="], + "ora/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], + "ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "ora/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + + "parse-json/json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], "socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], @@ -1362,11 +2235,7 @@ "socket.io-parser/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="], - "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], "tar/fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], @@ -1376,18 +2245,34 @@ "tar/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "v8-to-istanbul/convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "wrap-ansi/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "@angular-devkit/core/ajv/fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], + + "@angular-devkit/core/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "@inquirer/core/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - "@inquirer/core/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + "@listr2/prompt-adapter-inquirer/@inquirer/type/mute-stream": ["mute-stream@1.0.0", "", {}, "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA=="], "@npmcli/git/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="], @@ -1406,6 +2291,14 @@ "@tufjs/models/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "ajv-formats/ajv/fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], + + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "cacache/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -1418,26 +2311,46 @@ "cli-truncate/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], - "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], + "elasticsearch/chalk/ansi-styles": ["ansi-styles@2.2.1", "", {}, "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="], + + "elasticsearch/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "elasticsearch/chalk/strip-ansi": ["strip-ansi@3.0.1", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg=="], + + "elasticsearch/chalk/supports-color": ["supports-color@2.0.0", "", {}, "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="], + + "fast-json-stringify/ajv/fast-uri": ["fast-uri@3.0.6", "", {}, "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="], + + "fast-json-stringify/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "ignore-walk/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "karma-coverage/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "karma/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "karma/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], "karma/yargs/cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="], "karma/yargs/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + "log-update/slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], + "log-update/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.0.0", "", { "dependencies": { "get-east-asian-width": "^1.0.0" } }, "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA=="], + "log-update/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + "node-gyp/tar/chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], "node-gyp/tar/mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], @@ -1448,32 +2361,38 @@ "ora/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], - "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "ora/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - "string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "tar/fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], - "@inquirer/core/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], "@npmcli/package-json/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], "cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], - "karma/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], - "karma/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "elasticsearch/chalk/strip-ansi/ansi-regex": ["ansi-regex@2.1.1", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="], + + "karma/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "karma/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "karma/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], "karma/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], } } diff --git a/docker-compose.monitoring.yml b/docker-compose.monitoring.yml new file mode 100644 index 0000000..e24847c --- /dev/null +++ b/docker-compose.monitoring.yml @@ -0,0 +1,46 @@ +version: '3.8' + +services: + # Loki - Log aggregation + loki: + image: grafana/loki:2.9.2 + container_name: trading-bot-loki + ports: + - "3100:3100" + volumes: + - loki_data:/loki + - ./monitoring/loki:/etc/loki + command: -config.file=/etc/loki/loki-config.yaml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # Grafana - Visualization for logs and metrics + grafana: + image: grafana/grafana:10.2.0 + container_name: trading-bot-grafana + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_SECURITY_ADMIN_USER=admin + - GF_PATHS_PROVISIONING=/etc/grafana/provisioning + volumes: + - grafana_data:/var/lib/grafana + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + depends_on: + - loki + networks: + - trading-bot-network + +volumes: + loki_data: + grafana_data: + +networks: + trading-bot-network: + external: true diff --git a/docker-compose.monitoring.yml.old b/docker-compose.monitoring.yml.old new file mode 100644 index 0000000..e24847c --- /dev/null +++ b/docker-compose.monitoring.yml.old @@ -0,0 +1,46 @@ +version: '3.8' + +services: + # Loki - Log aggregation + loki: + image: grafana/loki:2.9.2 + container_name: trading-bot-loki + ports: + - "3100:3100" + volumes: + - loki_data:/loki + - ./monitoring/loki:/etc/loki + command: -config.file=/etc/loki/loki-config.yaml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # Grafana - Visualization for logs and metrics + grafana: + image: grafana/grafana:10.2.0 + container_name: trading-bot-grafana + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_SECURITY_ADMIN_USER=admin + - GF_PATHS_PROVISIONING=/etc/grafana/provisioning + volumes: + - grafana_data:/var/lib/grafana + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + depends_on: + - loki + networks: + - trading-bot-network + +volumes: + loki_data: + grafana_data: + +networks: + trading-bot-network: + external: true diff --git a/docker-compose.yml b/docker-compose.yml index e22df69..265e0aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,5 @@ +version: '3.8' + services: # Dragonfly - Redis replacement for caching and events dragonfly: @@ -108,7 +110,7 @@ services: image: dpage/pgadmin4:latest container_name: trading-bot-pgadmin environment: - PGADMIN_DEFAULT_EMAIL: boki@stare.gg + PGADMIN_DEFAULT_EMAIL: admin@tradingbot.local PGADMIN_DEFAULT_PASSWORD: admin123 PGADMIN_CONFIG_SERVER_MODE: 'False' PGADMIN_DISABLE_POSTFIX: 'true' @@ -131,7 +133,7 @@ services: ME_CONFIG_MONGODB_ADMINPASSWORD: trading_mongo_dev ME_CONFIG_MONGODB_SERVER: mongodb ME_CONFIG_MONGODB_PORT: 27017 - ME_CONFIG_BASICAUTH_USERNAME: boki + ME_CONFIG_BASICAUTH_USERNAME: admin ME_CONFIG_BASICAUTH_PASSWORD: admin123 ports: - "8081:8081" @@ -141,7 +143,7 @@ services: networks: - trading-bot-network - # Prometheus - Metrics collection (optional) + # Prometheus - Metrics collection prometheus: image: prom/prometheus:latest container_name: trading-bot-prometheus @@ -160,21 +162,42 @@ services: networks: - trading-bot-network - # Grafana - Metrics visualization (optional) + # Loki - Log aggregation + loki: + image: grafana/loki:2.9.2 + container_name: trading-bot-loki + ports: + - "3100:3100" + volumes: + - loki_data:/loki + - ./monitoring/loki:/etc/loki + command: -config.file=/etc/loki/loki-config.yaml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] + interval: 30s + timeout: 10s + retries: 3 + restart: unless-stopped + networks: + - trading-bot-network + + # Grafana - Visualization for logs and metrics grafana: - image: grafana/grafana:latest + image: grafana/grafana:10.2.0 container_name: trading-bot-grafana ports: - "3000:3000" environment: - - GF_SECURITY_ADMIN_PASSWORD=admin123 - - GF_SECURITY_ADMIN_USER=boki + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_SECURITY_ADMIN_USER=admin + - GF_PATHS_PROVISIONING=/etc/grafana/provisioning - GF_USERS_ALLOW_SIGN_UP=false volumes: - grafana_data:/var/lib/grafana - ./monitoring/grafana/provisioning:/etc/grafana/provisioning depends_on: - prometheus + - loki restart: unless-stopped networks: - trading-bot-network @@ -187,6 +210,7 @@ volumes: pgadmin_data: prometheus_data: grafana_data: + loki_data: networks: trading-bot-network: diff --git a/docker-compose.yml.backup b/docker-compose.yml.backup new file mode 100644 index 0000000..1b0562a --- /dev/null +++ b/docker-compose.yml.backup @@ -0,0 +1,244 @@ +services: + # Dragonfly - Redis replacement for caching and events + dragonfly: + image: docker.dragonflydb.io/dragonflydb/dragonfly:latest + container_name: trading-bot-dragonfly + ports: + - "6379:6379" + command: + - dragonfly + - --logtostderr + - --cache_mode=true + - --maxmemory=2gb + - --proactor_threads=8 + - --bind=0.0.0.0 + volumes: + - dragonfly_data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # PostgreSQL - Operational data (orders, positions, strategies) + postgres: + image: postgres:16-alpine + container_name: trading-bot-postgres + environment: + POSTGRES_DB: trading_bot + POSTGRES_USER: trading_user + POSTGRES_PASSWORD: trading_pass_dev + POSTGRES_INITDB_ARGS: "--encoding=UTF-8" + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./database/postgres/init:/docker-entrypoint-initdb.d + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U trading_user -d trading_bot"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # QuestDB - Time-series data (OHLCV, indicators, performance) + questdb: + image: questdb/questdb:latest + container_name: trading-bot-questdb + ports: + - "9000:9000" # Web console + - "8812:8812" # PostgreSQL wire protocol + - "9009:9009" # InfluxDB line protocol + volumes: + - questdb_data:/var/lib/questdb + environment: + - QDB_TELEMETRY_ENABLED=false + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/status"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # MongoDB - Document storage (sentiment, raw docs, unstructured data) + mongodb: + image: mongo:7-jammy + container_name: trading-bot-mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: trading_admin + MONGO_INITDB_ROOT_PASSWORD: trading_mongo_dev + MONGO_INITDB_DATABASE: trading_documents + ports: + - "27017:27017" + volumes: + - mongodb_data:/data/db + - ./database/mongodb/init:/docker-entrypoint-initdb.d + restart: unless-stopped + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # Redis Insight - GUI for Dragonfly debugging + redis-insight: + image: redislabs/redisinsight:latest + container_name: trading-bot-redis-insight + ports: + - "8001:8001" + environment: + - REDIS_HOSTS=local:dragonfly:6379 + depends_on: + - dragonfly + restart: unless-stopped + networks: + - trading-bot-network + + # PgAdmin - PostgreSQL GUI + pgadmin: + image: dpage/pgadmin4:latest + container_name: trading-bot-pgadmin + environment: + PGADMIN_DEFAULT_EMAIL: boki@stare.gg + PGADMIN_DEFAULT_PASSWORD: admin123 + PGADMIN_CONFIG_SERVER_MODE: 'False' + PGADMIN_DISABLE_POSTFIX: 'true' + ports: + - "8080:80" + volumes: + - pgadmin_data:/var/lib/pgadmin + depends_on: + - postgres + restart: unless-stopped + networks: + - trading-bot-network + + # Mongo Express - MongoDB GUI + mongo-express: + image: mongo-express:latest + container_name: trading-bot-mongo-express + environment: + ME_CONFIG_MONGODB_ADMINUSERNAME: trading_admin + ME_CONFIG_MONGODB_ADMINPASSWORD: trading_mongo_dev + ME_CONFIG_MONGODB_SERVER: mongodb + ME_CONFIG_MONGODB_PORT: 27017 + ME_CONFIG_BASICAUTH_USERNAME: boki + ME_CONFIG_BASICAUTH_PASSWORD: admin123 + ports: + - "8081:8081" + depends_on: + - mongodb + restart: unless-stopped + networks: + - trading-bot-network + + # Prometheus - Metrics collection (optional) + prometheus: + image: prom/prometheus:latest + container_name: trading-bot-prometheus + ports: + - "9090:9090" + volumes: + - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--web.enable-lifecycle' + restart: unless-stopped + networks: + - trading-bot-network + # Loki - Log aggregation + loki: + image: grafana/loki:2.9.2 + container_name: trading-bot-loki + ports: + - "3100:3100" + volumes: + - loki_data:/loki + - ./monitoring/loki:/etc/loki + command: -config.file=/etc/loki/loki-config.yaml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] + interval: 30s + timeout: 10s + retries: 3 + restart: unless-stopped + networks: + - trading-bot-network + + # Grafana - Visualization for logs and metrics + grafana: + image: grafana/grafana:10.2.0 + container_name: trading-bot-grafana + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_SECURITY_ADMIN_USER=admin + - GF_PATHS_PROVISIONING=/etc/grafana/provisioning + - GF_USERS_ALLOW_SIGN_UP=false + volumes: + - grafana_data:/var/lib/grafana + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + depends_on: + - prometheus + - loki + restart: unless-stopped + networks: + - trading-bot-network + +volumes: + postgres_data: + questdb_data: + dragonfly_data: + mongodb_data: + pgadmin_data: + prometheus_data: + grafana_data: + loki_data: + - "3100:3100" + volumes: + - loki_data:/loki + - ./monitoring/loki:/etc/loki + command: -config.file=/etc/loki/loki-config.yaml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # Grafana - Visualization for logs and metrics + grafana: + image: grafana/grafana:10.2.0 + container_name: trading-bot-grafana + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_SECURITY_ADMIN_USER=admin + - GF_PATHS_PROVISIONING=/etc/grafana/provisioning + volumes: + - grafana_data:/var/lib/grafana + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + depends_on: + - loki + networks: + - trading-bot-network + +networks: + trading-bot-network: + driver: bridge diff --git a/docker-compose.yml.new b/docker-compose.yml.new new file mode 100644 index 0000000..265e0aa --- /dev/null +++ b/docker-compose.yml.new @@ -0,0 +1,217 @@ +version: '3.8' + +services: + # Dragonfly - Redis replacement for caching and events + dragonfly: + image: docker.dragonflydb.io/dragonflydb/dragonfly:latest + container_name: trading-bot-dragonfly + ports: + - "6379:6379" + command: + - dragonfly + - --logtostderr + - --cache_mode=true + - --maxmemory=2gb + - --proactor_threads=8 + - --bind=0.0.0.0 + volumes: + - dragonfly_data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # PostgreSQL - Operational data (orders, positions, strategies) + postgres: + image: postgres:16-alpine + container_name: trading-bot-postgres + environment: + POSTGRES_DB: trading_bot + POSTGRES_USER: trading_user + POSTGRES_PASSWORD: trading_pass_dev + POSTGRES_INITDB_ARGS: "--encoding=UTF-8" + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./database/postgres/init:/docker-entrypoint-initdb.d + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U trading_user -d trading_bot"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # QuestDB - Time-series data (OHLCV, indicators, performance) + questdb: + image: questdb/questdb:latest + container_name: trading-bot-questdb + ports: + - "9000:9000" # Web console + - "8812:8812" # PostgreSQL wire protocol + - "9009:9009" # InfluxDB line protocol + volumes: + - questdb_data:/var/lib/questdb + environment: + - QDB_TELEMETRY_ENABLED=false + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9000/status"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # MongoDB - Document storage (sentiment, raw docs, unstructured data) + mongodb: + image: mongo:7-jammy + container_name: trading-bot-mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: trading_admin + MONGO_INITDB_ROOT_PASSWORD: trading_mongo_dev + MONGO_INITDB_DATABASE: trading_documents + ports: + - "27017:27017" + volumes: + - mongodb_data:/data/db + - ./database/mongodb/init:/docker-entrypoint-initdb.d + restart: unless-stopped + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 30s + timeout: 10s + retries: 3 + networks: + - trading-bot-network + + # Redis Insight - GUI for Dragonfly debugging + redis-insight: + image: redislabs/redisinsight:latest + container_name: trading-bot-redis-insight + ports: + - "8001:8001" + environment: + - REDIS_HOSTS=local:dragonfly:6379 + depends_on: + - dragonfly + restart: unless-stopped + networks: + - trading-bot-network + + # PgAdmin - PostgreSQL GUI + pgadmin: + image: dpage/pgadmin4:latest + container_name: trading-bot-pgadmin + environment: + PGADMIN_DEFAULT_EMAIL: admin@tradingbot.local + PGADMIN_DEFAULT_PASSWORD: admin123 + PGADMIN_CONFIG_SERVER_MODE: 'False' + PGADMIN_DISABLE_POSTFIX: 'true' + ports: + - "8080:80" + volumes: + - pgadmin_data:/var/lib/pgadmin + depends_on: + - postgres + restart: unless-stopped + networks: + - trading-bot-network + + # Mongo Express - MongoDB GUI + mongo-express: + image: mongo-express:latest + container_name: trading-bot-mongo-express + environment: + ME_CONFIG_MONGODB_ADMINUSERNAME: trading_admin + ME_CONFIG_MONGODB_ADMINPASSWORD: trading_mongo_dev + ME_CONFIG_MONGODB_SERVER: mongodb + ME_CONFIG_MONGODB_PORT: 27017 + ME_CONFIG_BASICAUTH_USERNAME: admin + ME_CONFIG_BASICAUTH_PASSWORD: admin123 + ports: + - "8081:8081" + depends_on: + - mongodb + restart: unless-stopped + networks: + - trading-bot-network + + # Prometheus - Metrics collection + prometheus: + image: prom/prometheus:latest + container_name: trading-bot-prometheus + ports: + - "9090:9090" + volumes: + - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + - '--storage.tsdb.path=/prometheus' + - '--web.console.libraries=/etc/prometheus/console_libraries' + - '--web.console.templates=/etc/prometheus/consoles' + - '--web.enable-lifecycle' + restart: unless-stopped + networks: + - trading-bot-network + + # Loki - Log aggregation + loki: + image: grafana/loki:2.9.2 + container_name: trading-bot-loki + ports: + - "3100:3100" + volumes: + - loki_data:/loki + - ./monitoring/loki:/etc/loki + command: -config.file=/etc/loki/loki-config.yaml + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:3100/ready"] + interval: 30s + timeout: 10s + retries: 3 + restart: unless-stopped + networks: + - trading-bot-network + + # Grafana - Visualization for logs and metrics + grafana: + image: grafana/grafana:10.2.0 + container_name: trading-bot-grafana + ports: + - "3000:3000" + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_SECURITY_ADMIN_USER=admin + - GF_PATHS_PROVISIONING=/etc/grafana/provisioning + - GF_USERS_ALLOW_SIGN_UP=false + volumes: + - grafana_data:/var/lib/grafana + - ./monitoring/grafana/provisioning:/etc/grafana/provisioning + depends_on: + - prometheus + - loki + restart: unless-stopped + networks: + - trading-bot-network + +volumes: + postgres_data: + questdb_data: + dragonfly_data: + mongodb_data: + pgadmin_data: + prometheus_data: + grafana_data: + loki_data: + +networks: + trading-bot-network: + driver: bridge diff --git a/docs/loki-logging.md b/docs/loki-logging.md new file mode 100644 index 0000000..19170d4 --- /dev/null +++ b/docs/loki-logging.md @@ -0,0 +1,171 @@ +# Loki Logging for Stock Bot + +This document outlines how to use the Loki logging system integrated with the Stock Bot platform. + +## Overview + +Loki is a horizontally-scalable, highly-available, multi-tenant log aggregation system inspired by Prometheus. It is designed to be very cost effective and easy to operate. Unlike other logging systems, Loki is built around the idea of only indexing metadata about your logs (labels), not the full text. This makes Loki more resource-efficient than traditional log storage systems. + +For Stock Bot, Loki provides: + +1. Centralized logging for all services +2. Log aggregation and filtering by service, level, and custom labels +3. Integration with Grafana for visualization +4. Query capabilities for log analysis +5. Alert capabilities for critical issues + +## Getting Started + +### Starting the Logging Stack + +```cmd +# Start the monitoring stack (includes Loki and Grafana) +scripts\docker.ps1 monitoring +``` + +You can also start Loki directly using Docker Compose: + +```cmd +# Start Loki service only +docker-compose up -d loki + +# Start Loki and Grafana +docker-compose up -d loki grafana +``` + +### Viewing Logs + +Once started: + +1. Access Grafana at http://localhost:3000 (login with admin/admin) +2. Navigate to the "Stock Bot Logs" dashboard +3. View and query your logs + +## Using the Logger in Your Services + +The Stock Bot logger has been enhanced to automatically send logs to Loki. Here's how to use it: + +```typescript +import { Logger, LogLevel } from '@stock-bot/utils'; + +// Create a logger for your service +const logger = new Logger('your-service-name', LogLevel.INFO); + +// Log at different levels +logger.debug('Detailed information for debugging'); +logger.info('General information about operations'); +logger.warn('Potential issues that don't affect operation'); +logger.error('Critical errors that require attention'); + +// Log with structured data (will be searchable in Loki) +logger.info('Processing trade', { + symbol: 'MSFT', + price: 410.75, + quantity: 50 +}); +``` + +## Configuration Options + +Logger configuration is managed through the `@stock-bot/config` package and can be set in your `.env` file: + +```bash +# Logging configuration +LOG_LEVEL=debug # debug, info, warn, error +LOG_CONSOLE=true # Log to console in addition to Loki +LOKI_HOST=localhost # Loki server hostname +LOKI_PORT=3100 # Loki server port +LOKI_RETENTION_DAYS=30 # Days to retain logs +LOKI_LABELS=environment=development,service=stock-bot # Default labels +LOKI_BATCH_SIZE=100 # Number of logs to batch before sending +LOKI_FLUSH_INTERVAL_MS=5000 # Max time to wait before sending logs +``` + +## Useful Loki Queries + +Inside Grafana, you can use these LogQL queries to analyze your logs: + +1. **All logs from a specific service**: + ``` + {service="market-data-gateway"} + ``` + +2. **All error logs across all services**: + ``` + {level="error"} + ``` + +3. **Logs containing specific text**: + ``` + {service="market-data-gateway"} |= "trade" + ``` + +4. **Count of error logs by service over time**: + ``` + sum by(service) (count_over_time({level="error"}[5m])) + ``` + +## Testing the Logging Integration + +A test script is provided to verify the logging integration: + +```bash +# Run from project root +bun run tools/test-loki-logging.ts +``` + +## Architecture + +Our logging implementation follows this architecture: + +``` +┌─────────────────┐ ┌─────────────────┐ +│ Trading Services│────►│ @stock-bot/utils│ +└─────────────────┘ │ Logger │ + └────────┬────────┘ + │ + ▼ +┌────────────────────────────────────────┐ +│ Loki │ +└────────────────┬───────────────────────┘ + │ + ▼ +┌────────────────────────────────────────┐ +│ Grafana │ +└────────────────────────────────────────┘ +``` + +## Adding New Dashboards + +To create new Grafana dashboards for log visualization: + +1. Build your dashboard in the Grafana UI +2. Export it to JSON +3. Add it to `monitoring/grafana/provisioning/dashboards/json/` +4. Restart the monitoring stack + +## Troubleshooting + +If logs aren't appearing in Grafana: + +1. Run the status check script to verify Loki and Grafana are working: + ```cmd + tools\check-loki-status.bat + ``` + +2. Check that Loki and Grafana containers are running: + ```cmd + docker ps | findstr "loki grafana" + ``` + +3. Verify .env configuration for Loki host and port: + ```cmd + type .env | findstr "LOKI_" + ``` + +4. Ensure your service has the latest @stock-bot/utils package + +5. Check for errors in the Loki container logs: + ```cmd + docker logs trading-bot-loki + ``` diff --git a/libs/config/.env.example b/libs/config/.env.example new file mode 100644 index 0000000..1100547 --- /dev/null +++ b/libs/config/.env.example @@ -0,0 +1,53 @@ +# Base environment variables for Stock Bot + +# Environment +NODE_ENV=development + +# Logging +LOG_LEVEL=debug + +# Database configuration +DRAGONFLY_HOST=localhost +DRAGONFLY_PORT=6379 +DRAGONFLY_PASSWORD= +DRAGONFLY_MAX_RETRIES_PER_REQUEST=3 + +TIMESCALE_HOST=localhost +TIMESCALE_PORT=5432 +TIMESCALE_DB=stockbot +TIMESCALE_USER=postgres +TIMESCALE_PASSWORD=postgres + +# Data providers +DEFAULT_DATA_PROVIDER=alpaca +ALPACA_API_KEY=your_alpaca_key_here +ALPACA_API_SECRET=your_alpaca_secret_here +POLYGON_API_KEY=your_polygon_key_here + +# Risk parameters +RISK_MAX_DRAWDOWN=0.05 +RISK_MAX_POSITION_SIZE=0.1 +RISK_MAX_LEVERAGE=1.5 +RISK_STOP_LOSS_DEFAULT=0.02 +RISK_TAKE_PROFIT_DEFAULT=0.05 + +# Market Data Gateway +SERVICE_PORT=4000 +WEBSOCKET_ENABLED=true +WEBSOCKET_PATH=/ws/market-data +WEBSOCKET_HEARTBEAT_INTERVAL=30000 +THROTTLING_MAX_REQUESTS=300 +THROTTLING_MAX_CONNECTIONS=5 +CACHING_ENABLED=true +CACHING_TTL_SECONDS=60 + +# Risk Guardian +RISK_CHECKS_PRE_TRADE=true +RISK_CHECKS_PORTFOLIO=true +RISK_CHECKS_LEVERAGE=true +RISK_CHECKS_CONCENTRATION=true +ALERTING_ENABLED=true +ALERTING_CRITICAL_THRESHOLD=0.8 +ALERTING_WARNING_THRESHOLD=0.6 +WATCHDOG_ENABLED=true +WATCHDOG_CHECK_INTERVAL=60 diff --git a/libs/config/.env.production.example b/libs/config/.env.production.example new file mode 100644 index 0000000..82e3435 --- /dev/null +++ b/libs/config/.env.production.example @@ -0,0 +1,28 @@ +# Production environment variables for Stock Bot + +# Environment +NODE_ENV=production + +# Logging +LOG_LEVEL=info + +# Database configuration (use environment-specific values) +DRAGONFLY_HOST=dragonfly.production +DRAGONFLY_PORT=6379 +DRAGONFLY_MAX_RETRIES_PER_REQUEST=5 + +TIMESCALE_HOST=timescale.production +TIMESCALE_PORT=5432 +TIMESCALE_DB=stockbot_prod + +# Risk parameters (more conservative for production) +RISK_MAX_DRAWDOWN=0.03 +RISK_MAX_POSITION_SIZE=0.05 +RISK_MAX_LEVERAGE=1.0 + +# Service settings +WEBSOCKET_HEARTBEAT_INTERVAL=15000 +THROTTLING_MAX_REQUESTS=500 +THROTTLING_MAX_CONNECTIONS=20 +CACHING_ENABLED=true +CACHING_TTL_SECONDS=30 diff --git a/libs/config/README.md b/libs/config/README.md new file mode 100644 index 0000000..65802ad --- /dev/null +++ b/libs/config/README.md @@ -0,0 +1,103 @@ +# @stock-bot/config + +A configuration management library for the Stock Bot trading platform. + +## Overview + +This library provides a centralized way to manage configurations across all Stock Bot microservices and components. It includes: + +- Environment-based configuration loading +- Strong TypeScript typing and validation using Zod +- Default configurations for services +- Environment variable parsing helpers +- Service-specific configuration modules + +## Usage + +### Basic Usage + +```typescript +import { databaseConfig, dataProviderConfigs, riskConfig } from '@stock-bot/config'; + +// Access database configuration +const dragonflyHost = databaseConfig.dragonfly.host; + +// Access data provider configuration +const alpacaApiKey = dataProviderConfigs.providers.find(p => p.name === 'alpaca')?.apiKey; + +// Access risk configuration +const maxPositionSize = riskConfig.maxPositionSize; +``` + +### Service-Specific Configuration + +```typescript +import { marketDataGatewayConfig, riskGuardianConfig } from '@stock-bot/config'; + +// Access Market Data Gateway configuration +const websocketPath = marketDataGatewayConfig.websocket.path; + +// Access Risk Guardian configuration +const preTradeValidation = riskGuardianConfig.riskChecks.preTradeValidation; +``` + +### Environment Variables + +The library automatically loads environment variables from `.env` files. You can create environment-specific files: + +- `.env` - Base environment variables +- `.env.development` - Development-specific variables +- `.env.production` - Production-specific variables +- `.env.local` - Local overrides (not to be committed to git) + +## Configuration Modules + +### Core Configuration + +- `Environment` - Enum for different environments +- `loadEnvVariables()` - Load environment variables from .env files +- `getEnvironment()` - Get the current environment +- `validateConfig()` - Validate configuration with Zod schema + +### Database Configuration + +- `databaseConfig` - Database connection settings (Dragonfly, TimescaleDB) + +### Data Provider Configuration + +- `dataProviderConfigs` - Settings for market data providers + +### Risk Configuration + +- `riskConfig` - Risk management parameters (max drawdown, position size, etc.) + +### Service-Specific Configuration + +- `marketDataGatewayConfig` - Configs for the Market Data Gateway service +- `riskGuardianConfig` - Configs for the Risk Guardian service + +## Extending + +To add a new service configuration: + +1. Create a new file in `src/services/` +2. Define a Zod schema for validation +3. Create loading and default configuration functions +4. Export from `src/services/index.ts` +5. The new configuration will be automatically available from the main package + +## Development + +```bash +# Install dependencies +bun install + +# Run tests +bun test + +# Type check +bun run type-check + +# Lint +bun run lint +``` diff --git a/libs/config/package.json b/libs/config/package.json new file mode 100644 index 0000000..fb660b7 --- /dev/null +++ b/libs/config/package.json @@ -0,0 +1,37 @@ +{ + "name": "@stock-bot/config", + "version": "1.0.0", + "description": "Configuration management library for Stock Bot platform", + "main": "src/index.ts", + "type": "module", + "scripts": { + "build": "tsc", + "test": "bun test", + "lint": "eslint src/**/*.ts", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "dotenv": "^16.3.1", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "typescript": "^5.3.0", + "eslint": "^8.56.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "bun-types": "^1.2.15" + }, + "keywords": [ + "configuration", + "settings", + "env", + "stock-bot" + ], + "exports": { + ".": { + "import": "./src/index.ts", + "require": "./dist/index.js" + } + } +} diff --git a/libs/config/setup.bat b/libs/config/setup.bat new file mode 100644 index 0000000..9b3e9b2 --- /dev/null +++ b/libs/config/setup.bat @@ -0,0 +1,24 @@ +@echo off +echo Building @stock-bot/config library... + +cd /d g:\repos\stock-bot +echo Installing dependencies... +bun install + +echo Running type check... +cd /d g:\repos\stock-bot\libs\config +bun run type-check + +echo Running tests... +bun test + +echo Setting up example configuration... +copy .env.example .env + +echo Running example to display configuration... +bun run src/example.ts + +echo. +echo Configuration library setup complete! +echo. +echo You can now import @stock-bot/config in your services. diff --git a/libs/config/src/core.test.ts b/libs/config/src/core.test.ts new file mode 100644 index 0000000..b3ae9fe --- /dev/null +++ b/libs/config/src/core.test.ts @@ -0,0 +1,113 @@ +/** + * Tests for the configuration library + */ +import { describe, expect, test, beforeAll, afterAll } from 'bun:test'; +import { + getEnvironment, + Environment, + validateConfig, + ConfigurationError, + loadEnvVariables, + getEnvVar, + getNumericEnvVar, + getBooleanEnvVar +} from './core'; + +import { databaseConfigSchema } from './types'; + +describe('Core configuration', () => { + // Save original environment variables + const originalEnv = { ...process.env }; + + // Setup test environment variables + beforeAll(() => { + process.env.NODE_ENV = 'testing'; + process.env.TEST_STRING = 'test-value'; + process.env.TEST_NUMBER = '42'; + process.env.TEST_BOOL_TRUE = 'true'; + process.env.TEST_BOOL_FALSE = 'false'; + }); + + // Restore original environment variables + afterAll(() => { + process.env = { ...originalEnv }; + }); + + test('getEnvironment returns correct environment', () => { + expect(getEnvironment()).toBe(Environment.Testing); + + // Test different environments + process.env.NODE_ENV = 'development'; + expect(getEnvironment()).toBe(Environment.Development); + + process.env.NODE_ENV = 'production'; + expect(getEnvironment()).toBe(Environment.Production); + + process.env.NODE_ENV = 'staging'; + expect(getEnvironment()).toBe(Environment.Staging); + + // Test default environment + process.env.NODE_ENV = 'unknown'; + expect(getEnvironment()).toBe(Environment.Development); + }); + + test('getEnvVar retrieves environment variables', () => { + expect(getEnvVar('TEST_STRING')).toBe('test-value'); + expect(getEnvVar('NON_EXISTENT')).toBeUndefined(); + expect(getEnvVar('NON_EXISTENT', false)).toBeUndefined(); + + // Test required variables + expect(() => getEnvVar('NON_EXISTENT', true)).toThrow(ConfigurationError); + }); + + test('getNumericEnvVar converts to number', () => { + expect(getNumericEnvVar('TEST_NUMBER')).toBe(42); + expect(getNumericEnvVar('NON_EXISTENT', 100)).toBe(100); + + // Test invalid number + process.env.INVALID_NUMBER = 'not-a-number'; + expect(() => getNumericEnvVar('INVALID_NUMBER')).toThrow(ConfigurationError); + }); + + test('getBooleanEnvVar converts to boolean', () => { + expect(getBooleanEnvVar('TEST_BOOL_TRUE')).toBe(true); + expect(getBooleanEnvVar('TEST_BOOL_FALSE')).toBe(false); + expect(getBooleanEnvVar('NON_EXISTENT', true)).toBe(true); + }); + + test('validateConfig validates against schema', () => { + // Valid config + const validConfig = { + dragonfly: { + host: 'localhost', + port: 6379, + maxRetriesPerRequest: 3 + }, + timescaleDB: { + host: 'localhost', + port: 5432, + database: 'stockbot', + user: 'postgres' + } + }; + + expect(() => validateConfig(validConfig, databaseConfigSchema)).not.toThrow(); + + // Invalid config (missing required field) + const invalidConfig = { + dragonfly: { + host: 'localhost', + // missing port + maxRetriesPerRequest: 3 + }, + timescaleDB: { + host: 'localhost', + port: 5432, + database: 'stockbot', + user: 'postgres' + } + }; + + expect(() => validateConfig(invalidConfig, databaseConfigSchema)).toThrow(ConfigurationError); + }); +}); diff --git a/libs/config/src/core.ts b/libs/config/src/core.ts new file mode 100644 index 0000000..65facb4 --- /dev/null +++ b/libs/config/src/core.ts @@ -0,0 +1,162 @@ +/** + * Core configuration module for the Stock Bot platform + */ +import { config as dotenvConfig } from 'dotenv'; +import path from 'path'; +import { z } from 'zod'; +import { Environment } from './types'; + +/** + * Represents an error related to configuration validation + */ +export class ConfigurationError extends Error { + constructor(message: string) { + super(message); + this.name = 'ConfigurationError'; + } +} + +/** + * Loads environment variables from .env files based on the current environment + */ +export function loadEnvVariables(envOverride?: string): void { + const env = envOverride || process.env.NODE_ENV || 'development'; + + // Order of loading: + // 1. .env (base environment variables) + // 2. .env.{environment} (environment-specific variables) + // 3. .env.local (local overrides, not to be committed) + + const envFiles = [ + '.env', + `.env.${env}`, + '.env.local' + ]; + + for (const file of envFiles) { + dotenvConfig({ path: path.resolve(process.cwd(), file) }); + } +} + +/** + * Gets the current environment from process.env.NODE_ENV + */ +export function getEnvironment(): Environment { + const env = process.env.NODE_ENV?.toLowerCase() || 'development'; + + switch (env) { + case 'development': + return Environment.Development; + case 'testing': + return Environment.Testing; + case 'staging': + return Environment.Staging; + case 'production': + return Environment.Production; + default: + return Environment.Development; + } +} + +/** + * Validates configuration using Zod schema + */ +export function validateConfig(config: unknown, schema: z.ZodSchema): T { + try { + return schema.parse(config); + } catch (error) { + if (error instanceof z.ZodError) { + const issues = error.issues.map(issue => + `${issue.path.join('.')}: ${issue.message}` + ).join('\n'); + + throw new ConfigurationError(`Configuration validation failed:\n${issues}`); + } + throw new ConfigurationError('Invalid configuration'); + } +} + +/** + * Retrieves an environment variable with validation + */ +export function getEnvVar(key: string, required: boolean = false): string | undefined { + const value = process.env[key]; + + if (required && (value === undefined || value === '')) { + throw new ConfigurationError(`Required environment variable ${key} is missing`); + } + + return value; +} + +/** + * Retrieves a numeric environment variable with validation + */ +export function getNumericEnvVar(key: string, defaultValue?: number): number { + const value = process.env[key]; + if (value === undefined || value === '') { + if (defaultValue !== undefined) { + return defaultValue; + } + throw new ConfigurationError(`Required numeric environment variable ${key} is missing`); + } + + const numValue = Number(value); + + if (isNaN(numValue)) { + throw new ConfigurationError(`Environment variable ${key} is not a valid number`); + } + + return numValue; +} + +/** + * Retrieves a boolean environment variable with validation + */ +export function getBooleanEnvVar(key: string, defaultValue?: boolean): boolean { + const value = process.env[key]; + if (value === undefined || value === '') { + if (defaultValue !== undefined) { + return defaultValue; + } + throw new ConfigurationError(`Required boolean environment variable ${key} is missing`); + } + + return value.toLowerCase() === 'true' || value === '1'; +} + +/** + * Creates a typed dynamic configuration loader for a specific service + */ +export function createConfigLoader( + serviceName: string, + schema: z.ZodSchema, + defaultConfig: Partial = {} +): () => T { + return (): T => { + try { + loadEnvVariables(); + const configEnvVar = `${serviceName.toUpperCase()}_CONFIG`; + let config = { ...defaultConfig } as unknown as T; + + // Try to load JSON from environment variable if available + const configJson = process.env[configEnvVar]; + if (configJson) { + try { + const parsedConfig = JSON.parse(configJson); + config = { ...config, ...parsedConfig }; + } catch (error) { + throw new ConfigurationError(`Invalid JSON in ${configEnvVar} environment variable`); + } + } + + // Validate and return the config + return validateConfig(config, schema); + } catch (error) { + if (error instanceof ConfigurationError) { + throw error; + } + throw new ConfigurationError(`Failed to load configuration for service ${serviceName}: ${error}`); + } + }; +} diff --git a/libs/config/src/data-providers.ts b/libs/config/src/data-providers.ts new file mode 100644 index 0000000..ada5071 --- /dev/null +++ b/libs/config/src/data-providers.ts @@ -0,0 +1,73 @@ +/** + * Data provider configurations for market data + */ +import { getEnvVar, validateConfig } from './core'; +import { dataProvidersConfigSchema, DataProvidersConfig, DataProviderConfig } from './types'; + +/** + * Default data provider configurations + */ +const defaultDataProviders: DataProviderConfig[] = [ + { + name: 'alpaca', + type: 'rest', + baseUrl: 'https://data.alpaca.markets/v1beta1', + apiKey: '', + apiSecret: '', + rateLimits: { + maxRequestsPerMinute: 200 + } + }, + { + name: 'polygon', + type: 'rest', + baseUrl: 'https://api.polygon.io/v2', + apiKey: '', + rateLimits: { + maxRequestsPerMinute: 5 + } + }, + { + name: 'alpaca-websocket', + type: 'websocket', + wsUrl: 'wss://stream.data.alpaca.markets/v2/iex', + apiKey: '', + apiSecret: '' + } +]; + +/** + * Load data provider configurations from environment variables + */ +export function loadDataProviderConfigs(): DataProvidersConfig { + // Get provider specific environment variables + const providers = defaultDataProviders.map(provider => { + const nameUpper = provider.name.toUpperCase().replace('-', '_'); + + const updatedProvider: DataProviderConfig = { + ...provider, + apiKey: getEnvVar(`${nameUpper}_API_KEY`) || provider.apiKey || '', + }; + + if (provider.apiSecret !== undefined) { + updatedProvider.apiSecret = getEnvVar(`${nameUpper}_API_SECRET`) || provider.apiSecret || ''; + } + + return updatedProvider; + }); + + // Load default provider from environment + const defaultProvider = getEnvVar('DEFAULT_DATA_PROVIDER') || 'alpaca'; + + const config: DataProvidersConfig = { + providers, + defaultProvider + }; + + return validateConfig(config, dataProvidersConfigSchema); +} + +/** + * Singleton data provider configurations + */ +export const dataProviderConfigs = loadDataProviderConfigs(); diff --git a/libs/config/src/database.ts b/libs/config/src/database.ts new file mode 100644 index 0000000..72d4474 --- /dev/null +++ b/libs/config/src/database.ts @@ -0,0 +1,52 @@ +/** + * Database configuration for Stock Bot services + */ +import { z } from 'zod'; +import { getEnvVar, getNumericEnvVar, validateConfig } from './core'; +import { databaseConfigSchema, DatabaseConfig } from './types'; + +/** + * Default database configuration + */ +const defaultDatabaseConfig: DatabaseConfig = { + dragonfly: { + host: 'localhost', + port: 6379, + maxRetriesPerRequest: 3 + }, + timescaleDB: { + host: 'localhost', + port: 5432, + database: 'stockbot', + user: 'postgres' + } +}; + +/** + * Load database configuration from environment variables + */ +export function loadDatabaseConfig(): DatabaseConfig { + const config: DatabaseConfig = { + dragonfly: { + host: getEnvVar('DRAGONFLY_HOST') || defaultDatabaseConfig.dragonfly.host, + port: getNumericEnvVar('DRAGONFLY_PORT', defaultDatabaseConfig.dragonfly.port), + password: getEnvVar('DRAGONFLY_PASSWORD'), + maxRetriesPerRequest: getNumericEnvVar('DRAGONFLY_MAX_RETRIES_PER_REQUEST', + defaultDatabaseConfig.dragonfly.maxRetriesPerRequest) + }, + timescaleDB: { + host: getEnvVar('TIMESCALE_HOST') || defaultDatabaseConfig.timescaleDB.host, + port: getNumericEnvVar('TIMESCALE_PORT', defaultDatabaseConfig.timescaleDB.port), + database: getEnvVar('TIMESCALE_DB') || defaultDatabaseConfig.timescaleDB.database, + user: getEnvVar('TIMESCALE_USER') || defaultDatabaseConfig.timescaleDB.user, + password: getEnvVar('TIMESCALE_PASSWORD') + } + }; + + return validateConfig(config, databaseConfigSchema); +} + +/** + * Singleton database configuration + */ +export const databaseConfig = loadDatabaseConfig(); diff --git a/libs/config/src/example.ts b/libs/config/src/example.ts new file mode 100644 index 0000000..0370d66 --- /dev/null +++ b/libs/config/src/example.ts @@ -0,0 +1,73 @@ +/** + * Example usage of the @stock-bot/config library + */ +import { + databaseConfig, + dataProviderConfigs, + riskConfig, + Environment, + getEnvironment, + marketDataGatewayConfig, + riskGuardianConfig, + ConfigurationError, + validateConfig +} from './index'; + +/** + * Display current configuration values + */ +export function printCurrentConfig(): void { + console.log('\n=== Stock Bot Configuration ==='); + + console.log('\nEnvironment:', getEnvironment()); + + console.log('\n--- Database Config ---'); + console.log('Dragonfly Host:', databaseConfig.dragonfly.host); + console.log('Dragonfly Port:', databaseConfig.dragonfly.port); + console.log('TimescaleDB Host:', databaseConfig.timescaleDB.host); + console.log('TimescaleDB Database:', databaseConfig.timescaleDB.database); + + console.log('\n--- Data Provider Config ---'); + console.log('Default Provider:', dataProviderConfigs.defaultProvider); + console.log('Providers:'); + dataProviderConfigs.providers.forEach(provider => { + console.log(` - ${provider.name} (${provider.type})`); + if (provider.baseUrl) console.log(` URL: ${provider.baseUrl}`); + if (provider.wsUrl) console.log(` WebSocket: ${provider.wsUrl}`); + }); + + console.log('\n--- Risk Config ---'); + console.log('Max Drawdown:', riskConfig.maxDrawdown * 100, '%'); + console.log('Max Position Size:', riskConfig.maxPositionSize * 100, '%'); + console.log('Max Leverage:', riskConfig.maxLeverage, 'x'); + console.log('Default Stop Loss:', riskConfig.stopLossDefault * 100, '%'); + console.log('Default Take Profit:', riskConfig.takeProfitDefault * 100, '%'); + + console.log('\n--- Market Data Gateway Config ---'); + console.log('Service Port:', marketDataGatewayConfig.service.port); + console.log('WebSocket Enabled:', marketDataGatewayConfig.websocket.enabled); + console.log('WebSocket Path:', marketDataGatewayConfig.websocket.path); + console.log('Caching Enabled:', marketDataGatewayConfig.caching.enabled); + console.log('Caching TTL:', marketDataGatewayConfig.caching.ttlSeconds, 'seconds'); + + console.log('\n--- Risk Guardian Config ---'); + console.log('Service Port:', riskGuardianConfig.service.port); + console.log('Pre-Trade Validation:', riskGuardianConfig.riskChecks.preTradeValidation); + console.log('Portfolio Validation:', riskGuardianConfig.riskChecks.portfolioValidation); + console.log('Alerting Enabled:', riskGuardianConfig.alerting.enabled); + console.log('Critical Threshold:', riskGuardianConfig.alerting.criticalThreshold * 100, '%'); +} + +// Execute example if this file is run directly +if (require.main === module) { + try { + printCurrentConfig(); + } catch (error) { + if (error instanceof ConfigurationError) { + console.error('Configuration Error:', error.message); + } else { + console.error('Error:', error); + } + process.exit(1); + } +} diff --git a/libs/config/src/index.ts b/libs/config/src/index.ts new file mode 100644 index 0000000..2ce4b96 --- /dev/null +++ b/libs/config/src/index.ts @@ -0,0 +1,24 @@ +/** + * @stock-bot/config + * + * Configuration management library for Stock Bot platform + */ + +// Core configuration functionality +export * from './core'; +export * from './types'; + +// Database configurations +export * from './database'; + +// Data provider configurations +export * from './data-providers'; + +// Risk management configurations +export * from './risk'; + +// Logging configurations +export * from './logging'; + +// Service-specific configurations +export * from './services'; diff --git a/libs/config/src/logging.ts b/libs/config/src/logging.ts new file mode 100644 index 0000000..a9ca718 --- /dev/null +++ b/libs/config/src/logging.ts @@ -0,0 +1,75 @@ +/** + * Loki logging configuration for Stock Bot platform + */ +import { z } from 'zod'; +import { getEnvVar, getNumericEnvVar, getBooleanEnvVar } from './core'; + +/** + * Loki configuration schema + */ +export const lokiConfigSchema = z.object({ + host: z.string().default('localhost'), + port: z.number().default(3100), + username: z.string().optional(), + password: z.string().optional(), + retentionDays: z.number().default(30), + labels: z.record(z.string()).default({}), + batchSize: z.number().default(100), + flushIntervalMs: z.number().default(5000) +}); + +export type LokiConfig = z.infer; + +/** + * Logging configuration schema + */ +export const loggingConfigSchema = z.object({ + level: z.enum(['debug', 'info', 'warn', 'error']).default('info'), + console: z.boolean().default(true), + loki: lokiConfigSchema +}); + +export type LoggingConfig = z.infer; + +/** + * Parse labels from environment variable string + * Format: key1=value1,key2=value2 + */ +function parseLabels(labelsStr?: string): Record { + if (!labelsStr) return {}; + + const labels: Record = {}; + labelsStr.split(',').forEach(labelPair => { + const [key, value] = labelPair.trim().split('='); + if (key && value) { + labels[key] = value; + } + }); + + return labels; +} + +/** + * Load logging configuration from environment variables + */ +export function loadLoggingConfig(): LoggingConfig { + return { + level: (getEnvVar('LOG_LEVEL') || 'info') as 'debug' | 'info' | 'warn' | 'error', + console: getBooleanEnvVar('LOG_CONSOLE', true), + loki: { + host: getEnvVar('LOKI_HOST') || 'localhost', + port: getNumericEnvVar('LOKI_PORT', 3100), + username: getEnvVar('LOKI_USERNAME'), + password: getEnvVar('LOKI_PASSWORD'), + retentionDays: getNumericEnvVar('LOKI_RETENTION_DAYS', 30), + labels: parseLabels(getEnvVar('LOKI_LABELS')), + batchSize: getNumericEnvVar('LOKI_BATCH_SIZE', 100), + flushIntervalMs: getNumericEnvVar('LOKI_FLUSH_INTERVAL_MS', 5000) + } + }; +} + +/** + * Singleton logging configuration + */ +export const loggingConfig = loadLoggingConfig(); diff --git a/libs/config/src/risk.ts b/libs/config/src/risk.ts new file mode 100644 index 0000000..f7798b3 --- /dev/null +++ b/libs/config/src/risk.ts @@ -0,0 +1,36 @@ +/** + * Risk management configuration for trading operations + */ +import { getNumericEnvVar, validateConfig } from './core'; +import { riskConfigSchema, RiskConfig } from './types'; + +/** + * Default risk configuration + */ +const defaultRiskConfig: RiskConfig = { + maxDrawdown: 0.05, + maxPositionSize: 0.1, + maxLeverage: 1, + stopLossDefault: 0.02, + takeProfitDefault: 0.05 +}; + +/** + * Load risk configuration from environment variables + */ +export function loadRiskConfig(): RiskConfig { + const config: RiskConfig = { + maxDrawdown: getNumericEnvVar('RISK_MAX_DRAWDOWN', defaultRiskConfig.maxDrawdown), + maxPositionSize: getNumericEnvVar('RISK_MAX_POSITION_SIZE', defaultRiskConfig.maxPositionSize), + maxLeverage: getNumericEnvVar('RISK_MAX_LEVERAGE', defaultRiskConfig.maxLeverage), + stopLossDefault: getNumericEnvVar('RISK_STOP_LOSS_DEFAULT', defaultRiskConfig.stopLossDefault), + takeProfitDefault: getNumericEnvVar('RISK_TAKE_PROFIT_DEFAULT', defaultRiskConfig.takeProfitDefault) + }; + + return validateConfig(config, riskConfigSchema); +} + +/** + * Singleton risk configuration + */ +export const riskConfig = loadRiskConfig(); diff --git a/libs/config/src/services/index.ts b/libs/config/src/services/index.ts new file mode 100644 index 0000000..d0aad5c --- /dev/null +++ b/libs/config/src/services/index.ts @@ -0,0 +1,5 @@ +/** + * Export all service-specific configurations + */ +export * from './market-data-gateway'; +export * from './risk-guardian'; diff --git a/libs/config/src/services/market-data-gateway.ts b/libs/config/src/services/market-data-gateway.ts new file mode 100644 index 0000000..16a44e0 --- /dev/null +++ b/libs/config/src/services/market-data-gateway.ts @@ -0,0 +1,106 @@ +/** + * Market Data Gateway service configuration + */ +import { z } from 'zod'; +import { getEnvVar, getNumericEnvVar, getBooleanEnvVar, createConfigLoader } from './core'; +import { Environment, BaseConfig } from './types'; +import { getEnvironment } from './core'; + +/** + * Market Data Gateway specific configuration schema + */ +export const marketDataGatewayConfigSchema = z.object({ + environment: z.nativeEnum(Environment), + logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'), + service: z.object({ + name: z.string().default('market-data-gateway'), + version: z.string().default('1.0.0'), + port: z.number().default(4000) + }), + websocket: z.object({ + enabled: z.boolean().default(true), + path: z.string().default('/ws/market-data'), + heartbeatInterval: z.number().default(30000) + }), + throttling: z.object({ + maxRequestsPerMinute: z.number().default(300), + maxConnectionsPerIP: z.number().default(5) + }), + caching: z.object({ + enabled: z.boolean().default(true), + ttlSeconds: z.number().default(60) + }) +}); + +/** + * Market Data Gateway configuration type + */ +export type MarketDataGatewayConfig = z.infer; + +/** + * Default Market Data Gateway configuration + */ +const defaultConfig: Partial = { + environment: getEnvironment(), + logLevel: 'info', + service: { + name: 'market-data-gateway', + version: '1.0.0', + port: 4000 + }, + websocket: { + enabled: true, + path: '/ws/market-data', + heartbeatInterval: 30000 // 30 seconds + }, + throttling: { + maxRequestsPerMinute: 300, + maxConnectionsPerIP: 5 + }, + caching: { + enabled: true, + ttlSeconds: 60 + } +}; + +/** + * Load Market Data Gateway configuration + */ +export function loadMarketDataGatewayConfig(): MarketDataGatewayConfig { + return { + environment: getEnvironment(), + logLevel: (getEnvVar('LOG_LEVEL') || defaultConfig.logLevel) as 'debug' | 'info' | 'warn' | 'error', + service: { + name: getEnvVar('SERVICE_NAME') || defaultConfig.service!.name, + version: getEnvVar('SERVICE_VERSION') || defaultConfig.service!.version, + port: getNumericEnvVar('SERVICE_PORT', defaultConfig.service!.port) + }, + websocket: { + enabled: getBooleanEnvVar('WEBSOCKET_ENABLED', defaultConfig.websocket!.enabled), + path: getEnvVar('WEBSOCKET_PATH') || defaultConfig.websocket!.path, + heartbeatInterval: getNumericEnvVar('WEBSOCKET_HEARTBEAT_INTERVAL', defaultConfig.websocket!.heartbeatInterval) + }, + throttling: { + maxRequestsPerMinute: getNumericEnvVar('THROTTLING_MAX_REQUESTS', defaultConfig.throttling!.maxRequestsPerMinute), + maxConnectionsPerIP: getNumericEnvVar('THROTTLING_MAX_CONNECTIONS', defaultConfig.throttling!.maxConnectionsPerIP) + }, + caching: { + enabled: getBooleanEnvVar('CACHING_ENABLED', defaultConfig.caching!.enabled), + ttlSeconds: getNumericEnvVar('CACHING_TTL_SECONDS', defaultConfig.caching!.ttlSeconds) + } + }; +} + +/** + * Creates a dynamic configuration loader for the Market Data Gateway + */ +export const createMarketDataGatewayConfig = createConfigLoader( + 'market-data-gateway', + marketDataGatewayConfigSchema, + defaultConfig +); + +/** + * Singleton Market Data Gateway configuration + */ +export const marketDataGatewayConfig = loadMarketDataGatewayConfig(); diff --git a/libs/config/src/services/risk-guardian.ts b/libs/config/src/services/risk-guardian.ts new file mode 100644 index 0000000..00fe887 --- /dev/null +++ b/libs/config/src/services/risk-guardian.ts @@ -0,0 +1,112 @@ +/** + * Risk Guardian service configuration + */ +import { z } from 'zod'; +import { getEnvVar, getNumericEnvVar, getBooleanEnvVar, createConfigLoader } from '../core'; +import { Environment, BaseConfig } from '../types'; +import { getEnvironment } from '../core'; + +/** + * Risk Guardian specific configuration schema + */ +export const riskGuardianConfigSchema = z.object({ + environment: z.nativeEnum(Environment), + logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'), + service: z.object({ + name: z.string().default('risk-guardian'), + version: z.string().default('1.0.0'), + port: z.number().default(4001) + }), + riskChecks: z.object({ + preTradeValidation: z.boolean().default(true), + portfolioValidation: z.boolean().default(true), + leverageValidation: z.boolean().default(true), + concentrationValidation: z.boolean().default(true) + }), + alerting: z.object({ + enabled: z.boolean().default(true), + criticalThreshold: z.number().default(0.8), + warningThreshold: z.number().default(0.6) + }), + watchdog: z.object({ + enabled: z.boolean().default(true), + checkIntervalSeconds: z.number().default(60) + }) +}); + +/** + * Risk Guardian configuration type + */ +export type RiskGuardianConfig = z.infer; + +/** + * Default Risk Guardian configuration + */ +const defaultConfig: Partial = { + environment: getEnvironment(), + logLevel: 'info', + service: { + name: 'risk-guardian', + version: '1.0.0', + port: 4001 + }, + riskChecks: { + preTradeValidation: true, + portfolioValidation: true, + leverageValidation: true, + concentrationValidation: true + }, + alerting: { + enabled: true, + criticalThreshold: 0.8, + warningThreshold: 0.6 + }, + watchdog: { + enabled: true, + checkIntervalSeconds: 60 + } +}; + +/** + * Load Risk Guardian configuration + */ +export function loadRiskGuardianConfig(): RiskGuardianConfig { + return { + environment: getEnvironment(), + logLevel: (getEnvVar('LOG_LEVEL') || defaultConfig.logLevel) as 'debug' | 'info' | 'warn' | 'error', + service: { + name: getEnvVar('SERVICE_NAME') || defaultConfig.service!.name, + version: getEnvVar('SERVICE_VERSION') || defaultConfig.service!.version, + port: getNumericEnvVar('SERVICE_PORT', defaultConfig.service!.port) + }, + riskChecks: { + preTradeValidation: getBooleanEnvVar('RISK_CHECKS_PRE_TRADE', defaultConfig.riskChecks!.preTradeValidation), + portfolioValidation: getBooleanEnvVar('RISK_CHECKS_PORTFOLIO', defaultConfig.riskChecks!.portfolioValidation), + leverageValidation: getBooleanEnvVar('RISK_CHECKS_LEVERAGE', defaultConfig.riskChecks!.leverageValidation), + concentrationValidation: getBooleanEnvVar('RISK_CHECKS_CONCENTRATION', defaultConfig.riskChecks!.concentrationValidation) + }, + alerting: { + enabled: getBooleanEnvVar('ALERTING_ENABLED', defaultConfig.alerting!.enabled), + criticalThreshold: getNumericEnvVar('ALERTING_CRITICAL_THRESHOLD', defaultConfig.alerting!.criticalThreshold), + warningThreshold: getNumericEnvVar('ALERTING_WARNING_THRESHOLD', defaultConfig.alerting!.warningThreshold) + }, + watchdog: { + enabled: getBooleanEnvVar('WATCHDOG_ENABLED', defaultConfig.watchdog!.enabled), + checkIntervalSeconds: getNumericEnvVar('WATCHDOG_CHECK_INTERVAL', defaultConfig.watchdog!.checkIntervalSeconds) + } + }; +} + +/** + * Creates a dynamic configuration loader for the Risk Guardian + */ +export const createRiskGuardianConfig = createConfigLoader( + 'risk-guardian', + riskGuardianConfigSchema, + defaultConfig +); + +/** + * Singleton Risk Guardian configuration + */ +export const riskGuardianConfig = loadRiskGuardianConfig(); diff --git a/libs/config/src/types.ts b/libs/config/src/types.ts new file mode 100644 index 0000000..0a5ff5f --- /dev/null +++ b/libs/config/src/types.ts @@ -0,0 +1,87 @@ +/** + * Configuration type definitions for the Stock Bot platform + */ +import { z } from 'zod'; + +/** + * Environment enum for different deployment environments + */ +export enum Environment { + Development = 'development', + Testing = 'testing', + Staging = 'staging', + Production = 'production' +} + +/** + * Common configuration interface for all service configs + */ +export interface BaseConfig { + environment: Environment; + logLevel: 'debug' | 'info' | 'warn' | 'error'; + service: { + name: string; + version: string; + port: number; + }; +} + +/** + * Database configuration schema + */ +export const databaseConfigSchema = z.object({ + dragonfly: z.object({ + host: z.string().default('localhost'), + port: z.number().default(6379), + password: z.string().optional(), + maxRetriesPerRequest: z.number().default(3) + }), + timescaleDB: z.object({ + host: z.string().default('localhost'), + port: z.number().default(5432), + database: z.string().default('stockbot'), + user: z.string().default('postgres'), + password: z.string().optional() + }) +}); + +/** + * Data provider configuration schema + */ +export const dataProviderSchema = z.object({ + name: z.string(), + type: z.enum(['rest', 'websocket', 'file']), + baseUrl: z.string().url().optional(), + wsUrl: z.string().url().optional(), + apiKey: z.string().optional(), + apiSecret: z.string().optional(), + refreshInterval: z.number().optional(), + rateLimits: z.object({ + maxRequestsPerMinute: z.number().optional(), + maxRequestsPerSecond: z.number().optional() + }).optional() +}); + +export const dataProvidersConfigSchema = z.object({ + providers: z.array(dataProviderSchema), + defaultProvider: z.string() +}); + +/** + * Risk management configuration schema + */ +export const riskConfigSchema = z.object({ + maxDrawdown: z.number().default(0.05), + maxPositionSize: z.number().default(0.1), + maxLeverage: z.number().default(1), + stopLossDefault: z.number().default(0.02), + takeProfitDefault: z.number().default(0.05) +}); + +/** + * Type definitions based on schemas + */ +export type DatabaseConfig = z.infer; +export type DataProviderConfig = z.infer; +export type DataProvidersConfig = z.infer; +export type RiskConfig = z.infer; diff --git a/libs/config/tsconfig.json b/libs/config/tsconfig.json new file mode 100644 index 0000000..51e0ff4 --- /dev/null +++ b/libs/config/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "declaration": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/libs/http-client/README.md b/libs/http-client/README.md new file mode 100644 index 0000000..8aa80dd --- /dev/null +++ b/libs/http-client/README.md @@ -0,0 +1,187 @@ +# @stock-bot/http-client + +High-performance HTTP client for Stock Bot microservices built on Bun's native fetch API. + +## Features + +- **Ultra-fast performance** - Built on Bun's native fetch implementation +- **Connection pooling** - Efficiently manages connections to prevent overwhelming servers +- **Automatic retries** - Handles transient network errors with configurable retry strategies +- **Timeout management** - Prevents requests from hanging indefinitely +- **Streaming support** - Efficient handling of large responses +- **TypeScript support** - Full type safety for all operations +- **Metrics & monitoring** - Built-in performance statistics + +## Installation + +```bash +bun add @stock-bot/http-client +``` + +## Basic Usage + +```typescript +import { BunHttpClient } from '@stock-bot/http-client'; + +// Create a client +const client = new BunHttpClient({ + baseURL: 'https://api.example.com', + timeout: 5000, + retries: 3 +}); + +// Make requests +async function fetchData() { + try { + // GET request + const response = await client.get('/users'); + console.log(response.data); + + // POST request with data + const createResponse = await client.post('/users', { + name: 'John Doe', + email: 'john@example.com' + }); + console.log(createResponse.data); + } catch (error) { + console.error('Request failed:', error.message); + } +} + +// Close when done +await client.close(); +``` + +## Advanced Configuration + +```typescript +const client = new BunHttpClient({ + baseURL: 'https://api.example.com', + timeout: 10000, + retries: 3, + retryDelay: 1000, + maxConcurrency: 20, + keepAlive: true, + headers: { + 'User-Agent': 'StockBot/1.0', + 'Authorization': 'Bearer token' + }, + validateStatus: (status) => status >= 200 && status < 300 +}); +``` + +## Connection Pooling + +The HTTP client automatically manages connection pooling with smart limits: + +```typescript +// Get connection statistics +const stats = client.getStats(); +console.log(`Active connections: ${stats.activeConnections}`); +console.log(`Success rate: ${stats.successfulRequests / (stats.successfulRequests + stats.failedRequests)}`); +console.log(`Average response time: ${stats.averageResponseTime}ms`); + +// Health check +const health = await client.healthCheck(); +if (health.healthy) { + console.log('HTTP client is healthy'); +} else { + console.log('HTTP client is degraded:', health.details); +} +``` + +## Event Handling + +```typescript +// Listen for specific events +client.on('response', ({ host, response }) => { + console.log(`Response from ${host}: ${response.status}`); +}); + +client.on('error', ({ host, error }) => { + console.log(`Error from ${host}: ${error.message}`); +}); + +client.on('retryAttempt', (data) => { + console.log(`Retrying request (${data.attempt}/${data.config.retries}): ${data.error.message}`); +}); +``` + +## API Reference + +### BunHttpClient + +Main HTTP client class with connection pooling and retry support. + +#### Methods + +- `request(config)`: Make a request with full configuration options +- `get(url, config?)`: Make a GET request +- `post(url, data?, config?)`: Make a POST request with data +- `put(url, data?, config?)`: Make a PUT request with data +- `patch(url, data?, config?)`: Make a PATCH request with data +- `delete(url, config?)`: Make a DELETE request +- `head(url, config?)`: Make a HEAD request +- `options(url, config?)`: Make an OPTIONS request +- `getStats()`: Get connection statistics +- `healthCheck()`: Check health of the client +- `close()`: Close all connections +- `setBaseURL(url)`: Update the base URL +- `setDefaultHeaders(headers)`: Update default headers +- `setTimeout(timeout)`: Update default timeout +- `create(config)`: Create a new instance with different config + +### Request Configuration + +```typescript +interface RequestConfig { + url: string; + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'; + headers?: Record; + body?: any; + timeout?: number; + retries?: number; + validateStatus?: (status: number) => boolean; + metadata?: Record; +} +``` + +### Response Object + +```typescript +interface HttpResponse { + data: T; + status: number; + statusText: string; + headers: Record; + config: RequestConfig; + timing: { + start: number; + end: number; + duration: number; + }; +} +``` + +## Error Handling + +```typescript +try { + const response = await client.get('/resource-that-might-fail'); + processData(response.data); +} catch (error) { + if (error instanceof TimeoutError) { + console.log('Request timed out'); + } else if (error instanceof RetryExhaustedError) { + console.log(`Request failed after ${error.config.retries} retries`); + } else if (error instanceof HttpClientError) { + console.log(`HTTP error: ${error.status} - ${error.message}`); + } else { + console.log('Unexpected error', error); + } +} +``` + +## License + +MIT diff --git a/libs/http-client/examples/basic-usage.ts b/libs/http-client/examples/basic-usage.ts new file mode 100644 index 0000000..43e52a4 --- /dev/null +++ b/libs/http-client/examples/basic-usage.ts @@ -0,0 +1,91 @@ +// Example usage of the @stock-bot/http-client library + +import { BunHttpClient } from '../src'; + +async function main() { + // Create a client instance + const client = new BunHttpClient({ + baseURL: 'https://api.polygon.io', + timeout: 10000, + retries: 2, + retryDelay: 500, + headers: { + 'X-API-Key': process.env.POLYGON_API_KEY || 'demo' + } + }); + + // Add event listeners for monitoring + client.on('response', ({host, response}) => { + console.log(`📦 Response from ${host}: ${response.status} (${response.timing.duration.toFixed(2)}ms)`); + }); + + client.on('error', ({host, error}) => { + console.error(`❌ Error from ${host}: ${error.message}`); + }); + + client.on('retryAttempt', ({attempt, config, delay}) => { + console.warn(`⚠️ Retry ${attempt}/${config.retries} for ${config.url} in ${delay}ms`); + }); + + try { + console.log('Fetching market data...'); + + // Make a GET request + const tickerResponse = await client.get('/v3/reference/tickers', { + headers: { + 'Accept': 'application/json' + } + }); + + console.log(`Found ${tickerResponse.data.results.length} tickers`); + console.log(`First ticker: ${JSON.stringify(tickerResponse.data.results[0], null, 2)}`); + + // Make a request that will fail + try { + await client.get('/non-existent-endpoint'); + } catch (error) { + console.log('Expected error caught:', error.message); + } + + // Multiple parallel requests + console.log('Making parallel requests...'); + const [aaplData, msftData, amznData] = await Promise.all([ + client.get('/v2/aggs/ticker/AAPL/range/1/day/2023-01-01/2023-01-15'), + client.get('/v2/aggs/ticker/MSFT/range/1/day/2023-01-01/2023-01-15'), + client.get('/v2/aggs/ticker/AMZN/range/1/day/2023-01-01/2023-01-15') + ]); + + console.log('Parallel requests completed:'); + console.log(`- AAPL: ${aaplData.status}, data points: ${aaplData.data.results?.length || 0}`); + console.log(`- MSFT: ${msftData.status}, data points: ${msftData.data.results?.length || 0}`); + console.log(`- AMZN: ${amznData.status}, data points: ${amznData.data.results?.length || 0}`); + + // Get client statistics + const stats = client.getStats(); + console.log('\nClient Stats:'); + console.log(`- Active connections: ${stats.activeConnections}`); + console.log(`- Total connections: ${stats.totalConnections}`); + console.log(`- Successful requests: ${stats.successfulRequests}`); + console.log(`- Failed requests: ${stats.failedRequests}`); + console.log(`- Average response time: ${stats.averageResponseTime.toFixed(2)}ms`); + console.log(`- Requests per second: ${stats.requestsPerSecond.toFixed(2)}`); + + // Health check + const health = await client.healthCheck(); + console.log(`\nClient health: ${health.healthy ? 'HEALTHY' : 'DEGRADED'}`); + console.log(`Health details: ${JSON.stringify(health.details, null, 2)}`); + + } catch (error) { + console.error('Error in example:', error); + } finally { + // Always close the client when done to clean up resources + await client.close(); + console.log('HTTP client closed'); + } +} + +// Run the example +main().catch(err => { + console.error('Example failed:', err); + process.exit(1); +}); diff --git a/libs/http-client/package.json b/libs/http-client/package.json new file mode 100644 index 0000000..7ac3189 --- /dev/null +++ b/libs/http-client/package.json @@ -0,0 +1,34 @@ +{ + "name": "@stock-bot/http-client", + "version": "1.0.0", + "description": "High-performance HTTP client for Stock Bot using Bun's native fetch", + "main": "src/index.ts", + "type": "module", + "scripts": { + "build": "tsc", + "test": "bun test", + "lint": "eslint src/**/*.ts", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "eventemitter3": "^5.0.1" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "typescript": "^5.3.0", + "eslint": "^8.56.0", + "@typescript-eslint/eslint-plugin": "^6.19.0", + "@typescript-eslint/parser": "^6.19.0", + "bun-types": "^1.2.15" + }, + "keywords": [ + "http-client", + "fetch", + "bun", + "performance", + "connection-pooling" + ], + "exports": { + ".": "./src/index.ts" + } +} diff --git a/libs/http-client/src/BunHttpClient.test.ts b/libs/http-client/src/BunHttpClient.test.ts new file mode 100644 index 0000000..90b930c --- /dev/null +++ b/libs/http-client/src/BunHttpClient.test.ts @@ -0,0 +1,182 @@ +import { describe, test, expect, mock, beforeEach, afterEach } from "bun:test"; +import { BunHttpClient, HttpClientError, TimeoutError } from "../src"; + +// Mock GlobalFetch to avoid making real network requests +const mockFetchSuccess = mock(() => + Promise.resolve(new Response( + JSON.stringify({ result: "success" }), + { status: 200, headers: { "content-type": "application/json" } } + )) +); + +const mockFetchFailure = mock(() => + Promise.resolve(new Response( + JSON.stringify({ error: "Not found" }), + { status: 404, headers: { "content-type": "application/json" } } + )) +); + +const mockFetchTimeout = mock(() => { + return new Promise((_, reject) => { + setTimeout(() => { + const error = new Error("Timeout"); + error.name = "AbortError"; + reject(error); + }, 10); + }); +}); + +describe("BunHttpClient", () => { + let client: BunHttpClient; + const originalFetch = global.fetch; + + beforeEach(() => { + // Create a fresh client for each test + client = new BunHttpClient({ + baseURL: "https://api.example.com", + timeout: 1000, + retries: 1 + }); + }); + + afterEach(async () => { + // Cleanup after each test + await client.close(); + global.fetch = originalFetch; + }); + + test("should make successful GET requests", async () => { + global.fetch = mockFetchSuccess; + + const response = await client.get("/users"); + + expect(response.status).toBe(200); + expect(response.data).toEqual({ result: "success" }); + expect(mockFetchSuccess).toHaveBeenCalledTimes(1); + }); + + test("should handle failed requests", async () => { + global.fetch = mockFetchFailure; + + try { + await client.get("/missing"); + expect("Should have thrown").toBe("But didn't"); + } catch (error) { + expect(error).toBeInstanceOf(HttpClientError); + expect(error.status).toBe(404); + } + + expect(mockFetchFailure).toHaveBeenCalledTimes(1); + }); + + test("should handle request timeouts", async () => { + global.fetch = mockFetchTimeout; + + try { + await client.get("/slow"); + expect("Should have thrown").toBe("But didn't"); + } catch (error) { + expect(error).toBeInstanceOf(TimeoutError); + } + }); + + test("should build full URLs properly", async () => { + global.fetch = mockFetchSuccess; + + await client.get("/users/123"); + expect(mockFetchSuccess).toHaveBeenCalledWith( + "https://api.example.com/users/123", + expect.objectContaining({ + method: "GET" + }) + ); + }); + + test("should make POST requests with body", async () => { + global.fetch = mockFetchSuccess; + + const data = { name: "John", email: "john@example.com" }; + await client.post("/users", data); + + expect(mockFetchSuccess).toHaveBeenCalledWith( + "https://api.example.com/users", + expect.objectContaining({ + method: "POST", + body: JSON.stringify(data) + }) + ); + }); + + test("should provide convenience methods for all HTTP verbs", async () => { + global.fetch = mockFetchSuccess; + + await client.get("/users"); + await client.post("/users", { name: "Test" }); + await client.put("/users/1", { name: "Updated" }); + await client.patch("/users/1", { status: "active" }); + await client.delete("/users/1"); + await client.head("/users"); + await client.options("/users"); + + expect(mockFetchSuccess).toHaveBeenCalledTimes(7); + }); + + test("should merge config options correctly", async () => { + global.fetch = mockFetchSuccess; + + await client.get("/users", { + headers: { "X-Custom": "Value" }, + timeout: 5000 + }); + + expect(mockFetchSuccess).toHaveBeenCalledWith( + "https://api.example.com/users", + expect.objectContaining({ + headers: expect.objectContaining({ + "X-Custom": "Value" + }) + }) + ); + }); + + test("should handle absolute URLs", async () => { + global.fetch = mockFetchSuccess; + + await client.get("https://other-api.com/endpoint"); + + expect(mockFetchSuccess).toHaveBeenCalledWith( + "https://other-api.com/endpoint", + expect.anything() + ); + }); + + test("should update configuration", async () => { + global.fetch = mockFetchSuccess; + + client.setBaseURL("https://new-api.com"); + client.setDefaultHeaders({ "Authorization": "Bearer token" }); + client.setTimeout(2000); + + await client.get("/resource"); + + expect(mockFetchSuccess).toHaveBeenCalledWith( + "https://new-api.com/resource", + expect.objectContaining({ + headers: expect.objectContaining({ + "Authorization": "Bearer token" + }) + }) + ); + }); + + test("should get connection stats", async () => { + global.fetch = mockFetchSuccess; + + await client.get("/users"); + const stats = client.getStats(); + + expect(stats).toHaveProperty("successfulRequests", 1); + expect(stats).toHaveProperty("activeConnections"); + expect(stats).toHaveProperty("averageResponseTime"); + }); +}); diff --git a/libs/http-client/src/BunHttpClient.ts b/libs/http-client/src/BunHttpClient.ts new file mode 100644 index 0000000..2fe3e1a --- /dev/null +++ b/libs/http-client/src/BunHttpClient.ts @@ -0,0 +1,199 @@ +import { EventEmitter } from 'eventemitter3'; +import type { + HttpClientConfig, + RequestConfig, + HttpResponse, + ConnectionStats, + HttpClientError, + TimeoutError +} from './types'; +import { ConnectionPool } from './ConnectionPool'; +import { RetryHandler } from './RetryHandler'; + +export class BunHttpClient extends EventEmitter { + private connectionPool: ConnectionPool; + private retryHandler: RetryHandler; + private defaultConfig: Required; + + constructor(config: HttpClientConfig = {}) { + super(); + + this.defaultConfig = { + baseURL: '', + timeout: 30000, + headers: {}, + retries: 3, + retryDelay: 1000, + maxConcurrency: 10, + keepAlive: true, + validateStatus: (status: number) => status < 400, + ...config + }; + + this.connectionPool = new ConnectionPool({ + maxConnections: this.defaultConfig.maxConcurrency, + maxConnectionsPerHost: Math.ceil(this.defaultConfig.maxConcurrency / 4), + keepAlive: this.defaultConfig.keepAlive, + maxIdleTime: 60000, + connectionTimeout: this.defaultConfig.timeout + }); + + this.retryHandler = new RetryHandler({ + maxRetries: this.defaultConfig.retries, + baseDelay: this.defaultConfig.retryDelay, + maxDelay: 30000, + exponentialBackoff: true + }); + + // Forward events from connection pool and retry handler + this.connectionPool.on('response', (data) => this.emit('response', data)); + this.connectionPool.on('error', (data) => this.emit('error', data)); + this.retryHandler.on('retryAttempt', (data) => this.emit('retryAttempt', data)); + this.retryHandler.on('retrySuccess', (data) => this.emit('retrySuccess', data)); + this.retryHandler.on('retryExhausted', (data) => this.emit('retryExhausted', data)); + } + + async request(config: RequestConfig): Promise> { + const fullConfig = this.mergeConfig(config); + + return this.retryHandler.execute(async () => { + const startTime = performance.now(); + + try { + // Add timing metadata + fullConfig.metadata = { + ...fullConfig.metadata, + startTime + }; + + const response = await this.connectionPool.request(fullConfig); + return response as HttpResponse; + + } catch (error: any) { + // Convert fetch errors to our error types + if (error.name === 'AbortError') { + throw new TimeoutError(fullConfig, fullConfig.timeout || this.defaultConfig.timeout); + } + + // Re-throw as HttpClientError if not already + if (!(error instanceof HttpClientError)) { + const httpError = new HttpClientError( + error.message || 'Request failed', + error.code, + error.status, + error.response, + fullConfig + ); + throw httpError; + } + + throw error; + } + }, fullConfig); + } + + // Convenience methods + async get(url: string, config?: Partial): Promise> { + return this.request({ ...config, url, method: 'GET' }); + } + + async post(url: string, data?: any, config?: Partial): Promise> { + return this.request({ ...config, url, method: 'POST', body: data }); + } + + async put(url: string, data?: any, config?: Partial): Promise> { + return this.request({ ...config, url, method: 'PUT', body: data }); + } + + async patch(url: string, data?: any, config?: Partial): Promise> { + return this.request({ ...config, url, method: 'PATCH', body: data }); + } + + async delete(url: string, config?: Partial): Promise> { + return this.request({ ...config, url, method: 'DELETE' }); + } + + async head(url: string, config?: Partial): Promise> { + return this.request({ ...config, url, method: 'HEAD' }); + } + + async options(url: string, config?: Partial): Promise> { + return this.request({ ...config, url, method: 'OPTIONS' }); + } + + private mergeConfig(config: RequestConfig): RequestConfig { + return { + timeout: this.defaultConfig.timeout, + retries: this.defaultConfig.retries, + headers: { ...this.defaultConfig.headers, ...config.headers }, + validateStatus: this.defaultConfig.validateStatus, + url: this.buildUrl(config.url), + ...config + }; + } + + private buildUrl(url: string): string { + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + + if (this.defaultConfig.baseURL) { + const baseURL = this.defaultConfig.baseURL.replace(/\/$/, ''); + const path = url.replace(/^\//, ''); + return `${baseURL}/${path}`; + } + + return url; + } + + // Configuration methods + setBaseURL(baseURL: string): void { + this.defaultConfig.baseURL = baseURL; + } + + setDefaultHeaders(headers: Record): void { + this.defaultConfig.headers = { ...this.defaultConfig.headers, ...headers }; + } + + setTimeout(timeout: number): void { + this.defaultConfig.timeout = timeout; + } + + setMaxConcurrency(maxConcurrency: number): void { + this.defaultConfig.maxConcurrency = maxConcurrency; + } + + // Statistics and monitoring + getStats(): ConnectionStats { + return this.connectionPool.getStats(); + } + + async healthCheck(): Promise<{ healthy: boolean; details: any }> { + return this.connectionPool.healthCheck(); + } + + // Lifecycle management + async close(): Promise { + await this.connectionPool.close(); + this.removeAllListeners(); + } + + // Create a new instance with different configuration + create(config: HttpClientConfig): BunHttpClient { + const mergedConfig = { ...this.defaultConfig, ...config }; + return new BunHttpClient(mergedConfig); + } + + // Interceptor-like functionality through events + onRequest(handler: (config: RequestConfig) => RequestConfig | Promise): void { + this.on('beforeRequest', handler); + } + + onResponse(handler: (response: HttpResponse) => HttpResponse | Promise): void { + this.on('afterResponse', handler); + } + + onError(handler: (error: any) => void): void { + this.on('requestError', handler); + } +} diff --git a/libs/http-client/src/ConnectionPool.ts b/libs/http-client/src/ConnectionPool.ts new file mode 100644 index 0000000..0edf953 --- /dev/null +++ b/libs/http-client/src/ConnectionPool.ts @@ -0,0 +1,331 @@ +import { EventEmitter } from 'eventemitter3'; +import type { + ConnectionPoolConfig, + ConnectionStats, + QueuedRequest, + RequestConfig +} from './types'; + +export class ConnectionPool extends EventEmitter { + private activeConnections = new Map(); + private requestQueue: QueuedRequest[] = []; + private stats = { + totalConnections: 0, + activeRequests: 0, + successfulRequests: 0, + failedRequests: 0, + totalResponseTime: 0, + requestCount: 0, + startTime: Date.now() + }; + private isProcessingQueue = false; + private queueProcessor?: NodeJS.Timeout; + + constructor(private config: ConnectionPoolConfig) { + super(); + this.startQueueProcessor(); + } + + async request(requestConfig: RequestConfig): Promise { + return new Promise((resolve, reject) => { + const host = this.extractHost(requestConfig.url); + const queuedRequest: QueuedRequest = { + id: this.generateRequestId(), + config: requestConfig, + resolve, + reject, + timestamp: Date.now(), + retryCount: 0, + host + }; + + this.requestQueue.push(queuedRequest); + this.processQueue(); + }); + } + + private async processQueue(): Promise { + if (this.isProcessingQueue || this.requestQueue.length === 0) { + return; + } + + this.isProcessingQueue = true; + + while (this.requestQueue.length > 0) { + const request = this.requestQueue.shift()!; + + try { + const currentConnections = this.activeConnections.get(request.host) || 0; + + // Check per-host connection limits + if (currentConnections >= this.config.maxConnectionsPerHost) { + this.requestQueue.unshift(request); + break; + } + + // Check global connection limit + const totalActive = Array.from(this.activeConnections.values()) + .reduce((sum, count) => sum + count, 0); + + if (totalActive >= this.config.maxConnections) { + this.requestQueue.unshift(request); + break; + } + + // Execute the request + this.executeRequest(request); + + } catch (error) { + request.reject(error); + } + } + + this.isProcessingQueue = false; + } + + private async executeRequest(request: QueuedRequest): Promise { + const { host, config } = request; + + // Increment active connections + this.activeConnections.set(host, (this.activeConnections.get(host) || 0) + 1); + this.stats.activeRequests++; + + const startTime = performance.now(); + + try { + // Build the full URL + const url = this.buildUrl(config.url, config); + + // Create abort controller for timeout + const controller = new AbortController(); + const timeoutId = config.timeout ? setTimeout(() => { + controller.abort(); + }, config.timeout) : undefined; + + // Make the fetch request + const response = await fetch(url, { + method: config.method || 'GET', + headers: this.buildHeaders(config.headers), + body: this.buildBody(config.body), + signal: controller.signal, + // Bun-specific optimizations + keepalive: this.config.keepAlive, + }); + + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Check if response is considered successful + const isSuccess = config.validateStatus + ? config.validateStatus(response.status) + : response.status < 400; + + if (!isSuccess) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + // Parse response data + const data = await this.parseResponse(response); + const endTime = performance.now(); + const duration = endTime - startTime; + + // Update stats + this.updateStats(true, duration); + + // Build response object + const httpResponse = { + data, + status: response.status, + statusText: response.statusText, + headers: this.parseHeaders(response.headers), + config, + timing: { + start: startTime, + end: endTime, + duration + } + }; + + this.emit('response', { host, response: httpResponse }); + request.resolve(httpResponse); + + } catch (error: any) { + const endTime = performance.now(); + const duration = endTime - startTime; + + this.updateStats(false, duration); + this.emit('error', { host, error, config }); + request.reject(error); + + } finally { + // Decrement active connections + this.activeConnections.set(host, Math.max(0, (this.activeConnections.get(host) || 0) - 1)); + this.stats.activeRequests = Math.max(0, this.stats.activeRequests - 1); + } + } + + private buildUrl(url: string, config: RequestConfig): string { + // If URL is already absolute, return as-is + if (url.startsWith('http://') || url.startsWith('https://')) { + return url; + } + + // If no base URL in config, assume it's a relative URL that needs a protocol + if (!url.startsWith('/')) { + url = '/' + url; + } + + return url; + } + + private buildHeaders(headers?: Record): HeadersInit { + return { + 'User-Agent': 'StockBot-HttpClient/1.0', + 'Accept': 'application/json', + 'Content-Type': 'application/json', + ...headers + }; + } + + private buildBody(body: any): BodyInit | undefined { + if (!body) return undefined; + if (typeof body === 'string') return body; + if (body instanceof FormData || body instanceof Blob) return body; + if (body instanceof ArrayBuffer || body instanceof Uint8Array) return body; + return JSON.stringify(body); + } + + private async parseResponse(response: Response): Promise { + const contentType = response.headers.get('content-type') || ''; + + if (contentType.includes('application/json')) { + return await response.json(); + } + + if (contentType.includes('text/')) { + return await response.text(); + } + + return await response.arrayBuffer(); + } + + private parseHeaders(headers: Headers): Record { + const result: Record = {}; + headers.forEach((value, key) => { + result[key] = value; + }); + return result; + } + + private extractHost(url: string): string { + try { + if (url.startsWith('http://') || url.startsWith('https://')) { + const urlObj = new URL(url); + return urlObj.host; + } + return 'default'; + } catch { + return 'default'; + } + } + + private generateRequestId(): string { + return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + } + + private updateStats(success: boolean, responseTime: number): void { + this.stats.requestCount++; + this.stats.totalResponseTime += responseTime; + + if (success) { + this.stats.successfulRequests++; + } else { + this.stats.failedRequests++; + } + } + + private startQueueProcessor(): void { + this.queueProcessor = setInterval(() => { + if (this.requestQueue.length > 0) { + this.processQueue(); + } + }, 10); // Process queue every 10ms for better responsiveness + } + + getStats(): ConnectionStats { + const totalActive = Array.from(this.activeConnections.values()) + .reduce((sum, count) => sum + count, 0); + + const averageResponseTime = this.stats.requestCount > 0 + ? this.stats.totalResponseTime / this.stats.requestCount + : 0; + + const utilization = this.config.maxConnections > 0 + ? totalActive / this.config.maxConnections + : 0; + + const elapsedTimeSeconds = (Date.now() - this.stats.startTime) / 1000; + const requestsPerSecond = elapsedTimeSeconds > 0 + ? this.stats.requestCount / elapsedTimeSeconds + : 0; + + return { + activeConnections: totalActive, + totalConnections: this.stats.totalConnections, + successfulRequests: this.stats.successfulRequests, + failedRequests: this.stats.failedRequests, + averageResponseTime, + connectionPoolUtilization: utilization, + requestsPerSecond + }; + } + + async close(): Promise { + // Stop queue processor + if (this.queueProcessor) { + clearInterval(this.queueProcessor); + this.queueProcessor = undefined; + } + + // Wait for pending requests to complete (with timeout) + const timeout = 30000; // 30 seconds + const startTime = Date.now(); + + while (this.requestQueue.length > 0 && Date.now() - startTime < timeout) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + + // Reject remaining requests + while (this.requestQueue.length > 0) { + const request = this.requestQueue.shift()!; + request.reject(new Error('Connection pool closing')); + } + + // Clear connections + this.activeConnections.clear(); + this.removeAllListeners(); + + this.emit('closed'); + } + + async healthCheck(): Promise<{ healthy: boolean; details: any }> { + const stats = this.getStats(); + const queueSize = this.requestQueue.length; + + const healthy = + stats.connectionPoolUtilization < 0.9 && // Less than 90% utilization + queueSize < 100 && // Queue not too large + stats.averageResponseTime < 5000; // Average response time under 5 seconds + + return { + healthy, + details: { + stats, + queueSize, + activeHosts: Array.from(this.activeConnections.keys()), + config: this.config + }, + }; + } +} diff --git a/libs/http-client/src/RetryHandler.ts b/libs/http-client/src/RetryHandler.ts new file mode 100644 index 0000000..e3bb734 --- /dev/null +++ b/libs/http-client/src/RetryHandler.ts @@ -0,0 +1,131 @@ +import { EventEmitter } from 'eventemitter3'; +import type { + RetryConfig, + RequestConfig, + HttpResponse, + HttpClientError, + TimeoutError, + RetryExhaustedError +} from './types'; + +export class RetryHandler extends EventEmitter { + private config: Required; + + constructor(config: Partial = {}) { + super(); + this.config = { + maxRetries: 3, + baseDelay: 1000, + maxDelay: 30000, + exponentialBackoff: true, + retryCondition: this.defaultRetryCondition, + ...config + }; + } + + async execute( + operation: () => Promise>, + requestConfig: RequestConfig + ): Promise> { + let lastError: any; + let attempt = 0; + + while (attempt <= this.config.maxRetries) { + try { + const result = await operation(); + + if (attempt > 0) { + this.emit('retrySuccess', { + requestConfig, + attempt, + result + }); + } + + return result; + } catch (error) { + lastError = error; + attempt++; + + // Check if we should retry + if ( + attempt > this.config.maxRetries || + !this.config.retryCondition(error) + ) { + break; + } + + // Calculate delay + const delay = this.calculateDelay(attempt); + + this.emit('retryAttempt', { + requestConfig, + attempt, + error, + delay + }); + + // Wait before retry + await this.delay(delay); + } + } + + // All retries exhausted + const finalError = new RetryExhaustedError(requestConfig, attempt, lastError); + this.emit('retryExhausted', { + requestConfig, + attempts: attempt, + finalError + }); + + throw finalError; + } + + private calculateDelay(attempt: number): number { + if (!this.config.exponentialBackoff) { + return this.config.baseDelay; + } + + // Exponential backoff with jitter + const exponentialDelay = this.config.baseDelay * Math.pow(2, attempt - 1); + const jitter = Math.random() * 0.1 * exponentialDelay; // 10% jitter + const totalDelay = Math.min(exponentialDelay + jitter, this.config.maxDelay); + + return Math.floor(totalDelay); + } + + private delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + private defaultRetryCondition(error: any): boolean { + // Network errors + if (error.code === 'ECONNRESET' || + error.code === 'ECONNREFUSED' || + error.code === 'ETIMEDOUT' || + error.code === 'ENOTFOUND') { + return true; + } + + // HTTP status codes that should be retried + const retryableStatuses = [408, 429, 500, 502, 503, 504]; + if (error.status && retryableStatuses.includes(error.status)) { + return true; + } + + // Timeout errors + if (error instanceof TimeoutError) { + return true; + } + + return false; + } + + updateConfig(config: Partial): void { + this.config = { ...this.config, ...config }; + } + + getConfig(): RetryConfig { + return { ...this.config }; + } +} diff --git a/libs/http-client/src/index.ts b/libs/http-client/src/index.ts new file mode 100644 index 0000000..0010e10 --- /dev/null +++ b/libs/http-client/src/index.ts @@ -0,0 +1,25 @@ +// Main exports +export { BunHttpClient } from './BunHttpClient'; +export { ConnectionPool } from './ConnectionPool'; +export { RetryHandler } from './RetryHandler'; + +// Type exports +export type { + HttpClientConfig, + RequestConfig, + HttpResponse, + ConnectionPoolConfig, + ConnectionStats, + QueuedRequest, + RetryConfig +} from './types'; + +// Error exports +export { + HttpClientError, + TimeoutError, + RetryExhaustedError +} from './types'; + +// Default export for convenience +export { BunHttpClient as default } from './BunHttpClient'; diff --git a/libs/http-client/src/types.ts b/libs/http-client/src/types.ts new file mode 100644 index 0000000..3f8ffbe --- /dev/null +++ b/libs/http-client/src/types.ts @@ -0,0 +1,104 @@ +// Type definitions for the HTTP client +export interface HttpClientConfig { + baseURL?: string; + timeout?: number; + headers?: Record; + retries?: number; + retryDelay?: number; + maxConcurrency?: number; + keepAlive?: boolean; + validateStatus?: (status: number) => boolean; +} + +export interface RequestConfig { + url: string; + method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'; + headers?: Record; + body?: any; + timeout?: number; + retries?: number; + metadata?: Record; + validateStatus?: (status: number) => boolean; +} + +export interface HttpResponse { + data: T; + status: number; + statusText: string; + headers: Record; + config: RequestConfig; + timing: { + start: number; + end: number; + duration: number; + }; +} + +export interface ConnectionPoolConfig { + maxConnections: number; + maxConnectionsPerHost: number; + keepAlive: boolean; + maxIdleTime: number; + connectionTimeout: number; +} + +export interface ConnectionStats { + activeConnections: number; + totalConnections: number; + successfulRequests: number; + failedRequests: number; + averageResponseTime: number; + connectionPoolUtilization: number; + requestsPerSecond: number; +} + +export interface QueuedRequest { + id: string; + config: RequestConfig; + resolve: (value: HttpResponse) => void; + reject: (error: any) => void; + timestamp: number; + retryCount: number; + host: string; +} + +export interface RetryConfig { + maxRetries: number; + baseDelay: number; + maxDelay: number; + exponentialBackoff: boolean; + retryCondition?: (error: any) => boolean; +} + +export class HttpClientError extends Error { + constructor( + message: string, + public code?: string, + public status?: number, + public response?: any, + public config?: RequestConfig + ) { + super(message); + this.name = 'HttpClientError'; + } +} + +export class TimeoutError extends HttpClientError { + constructor(config: RequestConfig, timeout: number) { + super(`Request timeout after ${timeout}ms`, 'TIMEOUT', undefined, undefined, config); + this.name = 'TimeoutError'; + } +} + +export class RetryExhaustedError extends HttpClientError { + constructor(config: RequestConfig, attempts: number, lastError: any) { + super( + `Request failed after ${attempts} attempts: ${lastError.message}`, + 'RETRY_EXHAUSTED', + lastError.status, + lastError.response, + config + ); + this.name = 'RetryExhaustedError'; + } +} diff --git a/libs/http-client/tsconfig.json b/libs/http-client/tsconfig.json new file mode 100644 index 0000000..34acc69 --- /dev/null +++ b/libs/http-client/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "**/*.test.ts"] +} diff --git a/libs/utils/package.json b/libs/utils/package.json index bf3d923..1e91b1d 100644 --- a/libs/utils/package.json +++ b/libs/utils/package.json @@ -9,9 +9,9 @@ "dev": "tsc --watch", "clean": "rm -rf dist", "test": "jest" - }, - "dependencies": { + }, "dependencies": { "@stock-bot/shared-types": "workspace:*", + "@stock-bot/config": "workspace:*", "date-fns": "^2.30.0" }, "devDependencies": { diff --git a/libs/utils/src/index.ts b/libs/utils/src/index.ts index 46bdbc7..93886b3 100644 --- a/libs/utils/src/index.ts +++ b/libs/utils/src/index.ts @@ -1,3 +1,4 @@ export * from './dateUtils'; export * from './financialUtils'; export * from './logger'; +export * from './lokiClient'; diff --git a/libs/utils/src/logger.ts b/libs/utils/src/logger.ts index 1f426ed..b7d6959 100644 --- a/libs/utils/src/logger.ts +++ b/libs/utils/src/logger.ts @@ -1,6 +1,20 @@ /** * Logger utility with consistent formatting and log levels + * Supports console and Loki logging */ +import { loggingConfig } from '@stock-bot/config'; +import { LokiClient } from './lokiClient'; + +// Singleton Loki client +let lokiClient: LokiClient | null = null; + +function getLokiClient(): LokiClient { + if (!lokiClient) { + lokiClient = new LokiClient(loggingConfig); + } + return lokiClient; +} + export class Logger { constructor(private serviceName: string, private level: LogLevel = LogLevel.INFO) {} @@ -26,22 +40,51 @@ export class Logger { const timestamp = new Date().toISOString(); const levelStr = LogLevel[level].padEnd(5); - const logMessage = `[${timestamp}] [${levelStr}] [${this.serviceName}] ${message}`; + const formattedArgs = args.length ? this.formatArgs(args) : ''; + const fullMessage = `${message}${formattedArgs}`; + const logMessage = `[${timestamp}] [${levelStr}] [${this.serviceName}] ${fullMessage}`; - switch (level) { - case LogLevel.ERROR: - console.error(logMessage, ...args); - break; - case LogLevel.WARN: - console.warn(logMessage, ...args); - break; - case LogLevel.INFO: - console.info(logMessage, ...args); - break; - case LogLevel.DEBUG: - default: - console.debug(logMessage, ...args); - break; + // Console logging + if (loggingConfig.console) { + switch (level) { + case LogLevel.ERROR: + console.error(logMessage); + break; + case LogLevel.WARN: + console.warn(logMessage); + break; + case LogLevel.INFO: + console.info(logMessage); + break; + case LogLevel.DEBUG: + default: + console.debug(logMessage); + break; + } + } + + // Loki logging + try { + const loki = getLokiClient(); + loki.log(LogLevel[level].toLowerCase(), fullMessage, this.serviceName); + } catch (error) { + console.error('Failed to send log to Loki:', error); + } + } + + private formatArgs(args: any[]): string { + try { + return args.map(arg => { + if (arg instanceof Error) { + return ` ${arg.message}\n${arg.stack}`; + } else if (typeof arg === 'object') { + return ` ${JSON.stringify(arg)}`; + } else { + return ` ${arg}`; + } + }).join(''); + } catch (error) { + return ` [Error formatting log arguments: ${error}]`; } } diff --git a/libs/utils/src/lokiClient.ts b/libs/utils/src/lokiClient.ts new file mode 100644 index 0000000..231ca3b --- /dev/null +++ b/libs/utils/src/lokiClient.ts @@ -0,0 +1,86 @@ +/** + * Loki client for sending logs to Grafana Loki + */ +import { LoggingConfig } from '@stock-bot/config'; + +export class LokiClient { + private batchQueue: any[] = []; + private flushInterval: NodeJS.Timeout; + private lokiUrl: string; + private authHeader?: string; + + constructor(private config: LoggingConfig) { + const { host, port, username, password } = config.loki; + + this.lokiUrl = `http://${host}:${port}/loki/api/v1/push`; + + if (username && password) { + const authString = Buffer.from(`${username}:${password}`).toString('base64'); + this.authHeader = `Basic ${authString}`; + } + + this.flushInterval = setInterval( + () => this.flush(), + config.loki.flushIntervalMs + ); + } + + async log(level: string, message: string, serviceName: string, labels: Record = {}) { + const timestamp = Date.now() * 1000000; // Loki expects nanoseconds + + this.batchQueue.push({ + streams: [{ + stream: { + level, + service: serviceName, + ...this.config.loki.labels, + ...labels, + }, + values: [[`${timestamp}`, message]], + }], + }); + + if (this.batchQueue.length >= this.config.loki.batchSize) { + await this.flush(); + } + } + + private async flush() { + if (this.batchQueue.length === 0) return; + + try { + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (this.authHeader) { + headers['Authorization'] = this.authHeader; + } + + const response = await fetch(this.lokiUrl, { + method: 'POST', + headers, + body: JSON.stringify({ + streams: this.batchQueue.flatMap(batch => batch.streams), + }), + }); + + if (!response.ok) { + console.error(`Failed to send logs to Loki: ${response.status} ${response.statusText}`); + const text = await response.text(); + if (text) { + console.error(text); + } + } + } catch (error) { + console.error('Error sending logs to Loki:', error); + } finally { + this.batchQueue = []; + } + } + + async destroy() { + clearInterval(this.flushInterval); + return this.flush(); + } +} diff --git a/monitoring/grafana/provisioning/dashboards/json/stock-bot-logs.json b/monitoring/grafana/provisioning/dashboards/json/stock-bot-logs.json new file mode 100644 index 0000000..abd376d --- /dev/null +++ b/monitoring/grafana/provisioning/dashboards/json/stock-bot-logs.json @@ -0,0 +1,211 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "Basic log viewer for Stock Bot services", + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": true, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "builder", + "expr": "{service=~\".+\"}", + "queryType": "range" + } + ], + "title": "All Service Logs", + "type": "logs" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unitScale": true + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "10.2.0", + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "builder", + "expr": "sum by(service) (count_over_time({level=\"error\"}[5m]))", + "legendFormat": "{{service}}", + "queryType": "range" + } + ], + "title": "Error Count by Service", + "type": "timeseries" + }, + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 3, + "options": { + "dedupStrategy": "none", + "enableLogDetails": true, + "prettifyLogMessage": false, + "showCommonLabels": false, + "showLabels": false, + "showTime": true, + "sortOrder": "Descending", + "wrapLogMessage": false + }, + "targets": [ + { + "datasource": { + "type": "loki", + "uid": "loki" + }, + "editorMode": "builder", + "expr": "{level=\"error\"}", + "queryType": "range" + } + ], + "title": "Error Logs", + "type": "logs" + } + ], + "refresh": "5s", + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Stock Bot Logs", + "uid": "stock-bot-logs", + "version": 1, + "weekStart": "" +} diff --git a/monitoring/grafana/provisioning/dashboards/stockbot.yml b/monitoring/grafana/provisioning/dashboards/stockbot.yml new file mode 100644 index 0000000..e2f96a3 --- /dev/null +++ b/monitoring/grafana/provisioning/dashboards/stockbot.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'Stock Bot Dashboards' + orgId: 1 + folder: 'Stock Bot' + type: file + disableDeletion: false + editable: true + options: + path: /etc/grafana/provisioning/dashboards/json diff --git a/monitoring/grafana/provisioning/datasources/loki.yml b/monitoring/grafana/provisioning/datasources/loki.yml new file mode 100644 index 0000000..8f87872 --- /dev/null +++ b/monitoring/grafana/provisioning/datasources/loki.yml @@ -0,0 +1,10 @@ +apiVersion: 1 + +datasources: + - name: Loki + type: loki + access: proxy + url: http://loki:3100 + jsonData: + maxLines: 1000 + isDefault: true diff --git a/monitoring/loki/loki-config.yaml b/monitoring/loki/loki-config.yaml new file mode 100644 index 0000000..482ffd7 --- /dev/null +++ b/monitoring/loki/loki-config.yaml @@ -0,0 +1,41 @@ +auth_enabled: false + +server: + http_listen_port: 3100 + grpc_listen_port: 9096 + +common: + path_prefix: /loki + storage: + filesystem: + chunks_directory: /loki/chunks + rules_directory: /loki/rules + replication_factor: 1 + ring: + instance_addr: 127.0.0.1 + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2023-01-01 + store: boltdb-shipper + object_store: filesystem + schema: v11 + index: + prefix: index_ + period: 24h + +ruler: + alertmanager_url: http://localhost:9093 + +# 30 days retention +limits_config: + retention_period: 720h + +query_range: + results_cache: + cache: + embedded_cache: + enabled: true + max_size_mb: 100 diff --git a/package.json b/package.json index e923936..070a8ce 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "stock-bot", "private": true, "version": "1.0.0", - "description": "Advanced trading bot with microservice architecture", "scripts": { + "description": "Advanced trading bot with microservice architecture", + "scripts": { "dev": "turbo run dev", "build": "turbo run build", "build:libs": "pwsh ./scripts/build-libs.ps1", @@ -11,7 +12,6 @@ "clean": "turbo run clean", "start": "turbo run start", "backtest": "turbo run backtest", - "docker:start": "pwsh ./scripts/docker.ps1 start", "docker:stop": "pwsh ./scripts/docker.ps1 stop", "docker:restart": "pwsh ./scripts/docker.ps1 restart", @@ -20,20 +20,19 @@ "docker:reset": "pwsh ./scripts/docker.ps1 reset", "docker:admin": "pwsh ./scripts/docker.ps1 admin", "docker:monitoring": "pwsh ./scripts/docker.ps1 monitoring", - "infra:up": "docker-compose up -d dragonfly postgres questdb mongodb", + "infra:up": "docker-compose up -d dragonfly postgres questdb mongodb", "infra:down": "docker-compose down", "infra:reset": "docker-compose down -v && docker-compose up -d dragonfly postgres questdb mongodb", - "dev:full": "npm run infra:up && npm run docker:admin && turbo run dev", "dev:clean": "npm run infra:reset && npm run dev:full" - }, "workspaces": [ + }, + "workspaces": [ "libs/*", - "packages/*", "apps/*/*" ], "devDependencies": { "@types/node": "^20.12.12", - "turbo": "^2.0.5", + "turbo": "^2.5.4", "typescript": "^5.4.5" }, "packageManager": "bun@1.1.12", diff --git a/scripts/docker.ps1 b/scripts/docker.ps1 index a6a7132..c9f4c44 100644 --- a/scripts/docker.ps1 +++ b/scripts/docker.ps1 @@ -68,7 +68,7 @@ switch ($Action) { "logs" { if ($Service) { - Write-Host "📋 Logs for $Service:" -ForegroundColor Cyan + Write-Host "📋 Logs for ${Service}:" -ForegroundColor Cyan Invoke-Expression "docker-compose $ComposeFiles logs -f $Service" } else { Write-Host "📋 All service logs:" -ForegroundColor Cyan @@ -99,18 +99,17 @@ switch ($Action) { Write-Host " PgAdmin: http://localhost:8080" Write-Host " Email: admin@tradingbot.local" Write-Host " Password: admin123" - } - - "monitoring" { + } "monitoring" { Write-Host "📊 Starting monitoring stack..." -ForegroundColor Green - Invoke-Expression "docker-compose $ComposeFiles --profile monitoring up -d" + Invoke-Expression "docker-compose $ComposeFiles up -d prometheus grafana loki" Write-Host "✅ Monitoring started!" -ForegroundColor Green Write-Host "" Write-Host "🔗 Monitoring Access:" -ForegroundColor Cyan Write-Host " Prometheus: http://localhost:9090" Write-Host " Grafana: http://localhost:3000" Write-Host " Username: admin" - Write-Host " Password: admin123" + Write-Host " Password: admin" + Write-Host " Loki: http://localhost:3100" } "help" { diff --git a/tools/check-loki-status.bat b/tools/check-loki-status.bat new file mode 100644 index 0000000..aead9dc --- /dev/null +++ b/tools/check-loki-status.bat @@ -0,0 +1,27 @@ +@echo off +REM Check Loki status and accessibility + +echo Checking Loki container status... +docker ps | findstr "loki" + +echo. +echo Testing Loki API... +curl -s http://localhost:3100/ready +echo. + +echo. +echo Testing Loki labels... +curl -s "http://localhost:3100/loki/api/v1/labels" | findstr /C:"service" +echo. + +echo. +echo Checking Grafana... +curl -s http://localhost:3000/api/health | findstr /C:"database" +echo. + +echo. +echo To view logs in Grafana: +echo 1. Open http://localhost:3000 in your browser +echo 2. Login with admin/admin +echo 3. Go to Explore and select Loki as the datasource +echo 4. Try the query: {service=~".+"} diff --git a/tools/test-loki-logging.ts b/tools/test-loki-logging.ts new file mode 100644 index 0000000..9120851 --- /dev/null +++ b/tools/test-loki-logging.ts @@ -0,0 +1,34 @@ +#!/usr/bin/env bun + +/** + * Test script to verify the Loki logging integration + */ +import { Logger, LogLevel } from '@stock-bot/utils'; + +// Create a logger for testing +const logger = new Logger('test-service', LogLevel.DEBUG); + +// Log test messages +logger.info('Starting test log messages...'); +logger.debug('This is a DEBUG level message'); +logger.info('This is an INFO level message'); +logger.warn('This is a WARNING level message'); +logger.error('This is an ERROR level message'); + +// Add some structured data +logger.info('Processing trade', { symbol: 'AAPL', price: 190.50, quantity: 100 }); +logger.info('Processing trade', { symbol: 'MSFT', price: 410.75, quantity: 50 }); + +// Simulate an error +try { + throw new Error('This is a simulated error'); +} catch (error) { + logger.error('An error occurred', error); +} + +logger.info('Test log messages complete. Check Grafana at http://localhost:3000 to view logs.'); + +// Wait to ensure all logs are sent +setTimeout(() => { + process.exit(0); +}, 1000);