UNPKG

@revmax/agent-sdk

Version:

Official Node.js SDK for RevMax - billing, customer management, and usage tracking

286 lines 9.83 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApiClient = void 0; const axios_1 = __importDefault(require("axios")); const errors_1 = require("./errors"); const telemetry_1 = require("./telemetry"); /** * Default API client options */ const DEFAULT_OPTIONS = { baseURL: 'https://betadevapi.userevmax.com/v1/sdk', timeout: 30000, retries: 0, retryDelay: 300, }; /** * Calculate exponential backoff time * @param retryCount - Number of retries attempted so far * @param initialDelay - Initial delay in milliseconds * @returns Delay in milliseconds */ function calculateBackoff(retryCount, initialDelay) { return initialDelay * Math.pow(2, retryCount); } /** * Sleep for a specified time * @param ms - Time in milliseconds * @returns Promise that resolves after the delay */ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Check if a request should be retried * @param error - Error from request * @returns Whether the request should be retried */ function isRetryable(error) { // Don't retry if it's a client error (except for rate limiting) if (error.response) { const status = error.response.status; // Retry on rate limiting or server errors return status === 429 || (status >= 500 && status < 600); } // Retry on network errors return !error.response; } /** * Normalize a URL path * @param url - URL path * @returns Normalized path */ function normalizePath(url) { // Remove leading slash if present const path = url.startsWith('/') ? url.substring(1) : url; // Remove query parameters const pathWithoutQuery = path.split('?')[0]; // Add leading slash return `/${pathWithoutQuery}`; } /** * API client for making HTTP requests with retry logic */ class ApiClient { /** * Create a new API client * @param auth - Authentication method * @param options - Client options * @param logger - Logger instance */ constructor(auth, options = {}, logger) { const mergedOptions = { ...DEFAULT_OPTIONS, ...options }; this.retries = mergedOptions.retries || 0; this.retryDelay = mergedOptions.retryDelay || 0; this.logger = logger; this.telemetry = new telemetry_1.Telemetry(mergedOptions.telemetry, logger); // Create axios instance with auth headers from auth method this.axios = axios_1.default.create({ baseURL: mergedOptions.baseURL, timeout: mergedOptions.timeout, headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'User-Agent': 'revmax-node/1.0.0', ...mergedOptions.headers, ...auth.getHeaders(), // This now only includes the 'revx-api-key' header }, }); // Log requests this.axios.interceptors.request.use((config) => { // Generate request ID and add to headers const requestId = this.telemetry.generateRequestId(); config.headers = config.headers || {}; config.headers['X-Request-ID'] = requestId; // Store tracking info on config for later use const customConfig = config; customConfig.metadata = customConfig.metadata || {}; customConfig.metadata.requestId = requestId; // Start telemetry tracking if (config.method && config.url) { const { isTracked } = this.telemetry.startRequest(config.method, config.url, requestId); customConfig.metadata.telemetryTracked = isTracked; } this.logger.debug(`Request: ${config.method?.toUpperCase()} ${config.url}`, { requestId }); return config; }); // Log responses this.axios.interceptors.response.use((response) => { const customConfig = response.config; const requestId = response.config.headers?.['X-Request-ID']; const isTracked = customConfig.metadata?.telemetryTracked; // End telemetry tracking for successful requests if (isTracked && requestId) { this.telemetry.endRequest(requestId, response.status); } this.logger.debug(`Response: ${response.status} ${response.config.url}`, { requestId }); return response; }, (error) => { // Extract request ID from the failed request const customConfig = error.config; const requestId = error.config?.headers?.['X-Request-ID']; const isTracked = customConfig?.metadata?.telemetryTracked; const retryCount = customConfig?.metadata?.retryCount || 0; // End telemetry tracking for failed requests if (isTracked && requestId) { this.telemetry.endRequest(requestId, error.response?.status, error, retryCount); } this.logger.error(`Error: ${error.message}`, { requestId, status: error.response?.status, data: error.response?.data, }); return Promise.reject(error); }); } /** * Make a request with retry logic * @param config - Request configuration * @returns API response */ async request(config) { let lastError; // Ensure metadata object exists config.metadata = config.metadata || {}; for (let retry = 0; retry <= this.retries; retry++) { try { // Track retry count for telemetry config.metadata.retryCount = retry; if (retry > 0) { const requestId = config.headers?.['X-Request-ID']; this.logger.info(`Retry attempt ${retry}/${this.retries}`, { requestId }); } return await this.axios.request(config); } catch (error) { lastError = error; // Check if we should retry if (retry < this.retries && isRetryable(error)) { // Calculate backoff time const backoff = calculateBackoff(retry, this.retryDelay); // Use retry-after header if available if (error.response?.headers?.['retry-after']) { const retryAfter = parseInt(error.response.headers['retry-after'], 10) * 1000; await sleep(retryAfter); } else { await sleep(backoff); } continue; } break; } } // If we get here, all retries failed or we didn't retry throw (0, errors_1.parseApiError)(lastError); } /** * Make a GET request * @param url - Endpoint URL * @param params - Query parameters * @param config - Additional request configuration * @returns API response */ async get(url, params, config) { url = this.ensureURLPrefix(url); const response = await this.request({ method: 'GET', url, params, ...config, }); return response.data; } /** * Make a POST request * @param url - Endpoint URL * @param data - Request body * @param config - Additional request configuration * @returns API response */ async post(url, data, config) { url = this.ensureURLPrefix(url); const response = await this.request({ method: 'POST', url, data, ...config, }); return response.data; } /** * Make a PUT request * @param url - Endpoint URL * @param data - Request body * @param config - Additional request configuration * @returns API response */ async put(url, data, config) { url = this.ensureURLPrefix(url); const response = await this.request({ method: 'PUT', url, data, ...config, }); return response.data; } /** * Make a PATCH request * @param url - Endpoint URL * @param data - Request body * @param config - Additional request configuration * @returns API response */ async patch(url, data, config) { url = this.ensureURLPrefix(url); const response = await this.request({ method: 'PATCH', url, data, ...config, }); return response.data; } /** * Make a DELETE request * @param url - Endpoint URL * @param config - Additional request configuration * @returns API response */ async delete(url, config) { url = this.ensureURLPrefix(url); const response = await this.request({ method: 'DELETE', url, ...config, }); return response.data; } /** * Ensure URL has proper prefix (leading slash) * @param url - URL to normalize * @returns Normalized URL */ ensureURLPrefix(url) { return url.startsWith('/') ? url : `/${url}`; } /** * Get telemetry statistics * @returns Current telemetry stats */ getTelemetryStats() { return this.telemetry.getStats(); } /** * Reset telemetry statistics */ resetTelemetryStats() { this.telemetry.resetStats(); } } exports.ApiClient = ApiClient; //# sourceMappingURL=api.js.map