import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import axios, { AxiosInstance } from 'axios'; import PQueue from 'p-queue'; @Injectable() export class MoexHttpClient { private readonly logger = new Logger(MoexHttpClient.name); private readonly client: AxiosInstance; private readonly queue: PQueue; private circuitOpen = false; private circuitErrorCount = 0; private readonly threshold: number; private readonly resetMs: number; constructor(private configService: ConfigService) { const baseUrl = this.configService.get('app.moex.baseUrl')!; this.threshold = this.configService.get('app.moex.circuitBreakerThreshold', 5); this.resetMs = this.configService.get('app.moex.circuitBreakerResetSeconds', 30) * 1000; const rateLimit = this.configService.get('app.moex.rateLimit', 10); this.client = axios.create({ baseURL: baseUrl, timeout: 10000, paramsSerializer: { indexes: null }, }); this.queue = new PQueue({ interval: 1000, intervalCap: rateLimit, }); } async request(path: string, params?: Record): Promise { if (this.circuitOpen) { throw new Error('Circuit breaker is open — MOEX requests paused'); } return this.queue.add(async () => { try { const jsonPath = path + '.json'; const response = await this.client.get(jsonPath, { params: { ...params, 'iss.meta': 'off' }, }); this.circuitErrorCount = 0; return response.data as T; } catch (error) { this.circuitErrorCount++; if (this.circuitErrorCount >= this.threshold) { this.circuitOpen = true; this.logger.warn(`Circuit breaker opened after ${this.threshold} errors`); setTimeout(() => { this.circuitOpen = false; this.circuitErrorCount = 0; this.logger.log('Circuit breaker reset'); }, this.resetMs); } throw error; } }) as Promise; } extractTable(data: Record, name: string): Record[] { const table = data[name] as Record | undefined; if (!table || !table.columns || !table.data) return []; const columns = table.columns as string[]; const rows = table.data as unknown[][]; return rows.map((row) => { const obj: Record = {}; columns.forEach((col, i) => { obj[col] = row[i]; }); return obj; }); } }