no idea- added loki and other stuff to market-data-gateway, also added config lib
This commit is contained in:
parent
b957fb99aa
commit
1b71fc87ab
72 changed files with 6178 additions and 153 deletions
199
libs/http-client/src/BunHttpClient.ts
Normal file
199
libs/http-client/src/BunHttpClient.ts
Normal file
|
|
@ -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<HttpClientConfig>;
|
||||
|
||||
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<T = any>(config: RequestConfig): Promise<HttpResponse<T>> {
|
||||
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<T>;
|
||||
|
||||
} 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<T = any>(url: string, config?: Partial<RequestConfig>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, url, method: 'GET' });
|
||||
}
|
||||
|
||||
async post<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, url, method: 'POST', body: data });
|
||||
}
|
||||
|
||||
async put<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, url, method: 'PUT', body: data });
|
||||
}
|
||||
|
||||
async patch<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, url, method: 'PATCH', body: data });
|
||||
}
|
||||
|
||||
async delete<T = any>(url: string, config?: Partial<RequestConfig>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, url, method: 'DELETE' });
|
||||
}
|
||||
|
||||
async head<T = any>(url: string, config?: Partial<RequestConfig>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...config, url, method: 'HEAD' });
|
||||
}
|
||||
|
||||
async options<T = any>(url: string, config?: Partial<RequestConfig>): Promise<HttpResponse<T>> {
|
||||
return this.request<T>({ ...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<string, string>): 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<void> {
|
||||
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<RequestConfig>): void {
|
||||
this.on('beforeRequest', handler);
|
||||
}
|
||||
|
||||
onResponse(handler: (response: HttpResponse) => HttpResponse | Promise<HttpResponse>): void {
|
||||
this.on('afterResponse', handler);
|
||||
}
|
||||
|
||||
onError(handler: (error: any) => void): void {
|
||||
this.on('requestError', handler);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue