UNPKG

@montarist/nilvera-api

Version:

An unofficial SDK for integrating with Nilvera e-Invoice, e-Archive services

235 lines (234 loc) 9.17 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 nilvera_api_error_1 = require("../constants/errors/nilvera-api.error"); const types_1 = require("../types"); const DEFAULT_RETRY_CONFIG = { maxRetries: 3, retryDelay: 1000, // 1 second retryableStatuses: [408, 429, 500, 502, 503, 504] // Common retryable status codes }; /** * @class ApiClient * @description A client for making HTTP requests with automatic token, API key handling */ class ApiClient { /** * @constructor * @param {string} baseUrl - The base URL for all API requests * @param {string} apiKey - The API key for authentication * @param {RetryConfig} [retryConfig] - Optional retry configuration */ constructor(baseUrl, apiKey, mode = types_1.Mode.DEVELOPMENT, retryConfig) { this.apiKey = apiKey; this.lastCurlCommand = ''; this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...retryConfig }; this.mode = mode; this.client = axios_1.default.create({ baseURL: baseUrl, headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}`, }, }); this.setupInterceptors(); } /** * @private * @method sleep * @description Utility method to pause execution */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * @private * @method shouldRetry * @description Determines if a request should be retried based on the error */ shouldRetry(error, attemptNumber) { if (attemptNumber >= this.retryConfig.maxRetries) { return false; } if (!error.response) { // Network errors should be retried return true; } return this.retryConfig.retryableStatuses.includes(error.response.status); } /** * @private * @method executeWithRetry * @description Executes a request with retry logic */ async executeWithRetry(operation, operationName) { let lastError = null; let attempt = 1; while (attempt <= this.retryConfig.maxRetries) { try { return await operation(); } catch (error) { if (error instanceof nilvera_api_error_1.NilveraApiError) { throw error; } lastError = error; if (!this.shouldRetry(lastError, attempt)) { break; } if (this.mode === types_1.Mode.DEVELOPMENT) { console.log(`Attempt ${attempt} failed for ${operationName}. Retrying in ${this.retryConfig.retryDelay}ms...`); } await this.sleep(this.retryConfig.retryDelay); attempt++; } } if (lastError) { if (this.mode === types_1.Mode.DEVELOPMENT) { console.log(`All retry attempts failed for ${operationName}`); } throw this.handleAxiosError(lastError); } throw new Error('Unexpected error occurred'); } /** * @private * @method handleAxiosError * @description Handles Axios errors and converts them to NilveraApiError */ handleAxiosError(error) { if (error.response) { // Server responded with error status const { data, status, config } = error.response; throw new nilvera_api_error_1.NilveraApiError(data?.message || error.message, status, data?.code, data, config?.url, config?.data ? JSON.parse(config.data) : undefined, this.lastCurlCommand); } else if (error.request) { // Request was made but no response received throw new nilvera_api_error_1.NilveraApiError('No response received from server', 0, 'NO_RESPONSE', error.request, undefined, undefined, this.lastCurlCommand); } else { // Error in request configuration throw new nilvera_api_error_1.NilveraApiError(error.message, 0, 'REQUEST_ERROR', undefined, undefined, undefined, this.lastCurlCommand); } } /** * @private * @method generateCurlCommand * @description Generates a curl command from the request configuration */ generateCurlCommand(config) { const method = config.method?.toUpperCase(); const url = `${config.baseURL}${config.url}`; const headers = { ...config.headers, Authorization: `Bearer ${this.apiKey}`, }; const data = config.data ? JSON.stringify(config.data, null, 4) : null; let curl = `curl --location --request ${method} '${url}' \\`; Object.entries(headers).forEach(([key, value]) => { curl += `\n--header '${key}: ${value}' \\`; }); if (data) { curl += `\n--data-raw '${data}'`; } return curl; } /** * @private * @method setupInterceptors * @description Configures request interceptors for authentication headers and curl command generation */ setupInterceptors() { this.client.interceptors.request.use((config) => { const headers = { ...config.headers, Authorization: `Bearer ${this.apiKey}`, }; Object.entries(headers).forEach(([key, value]) => { config.headers.set(key, value); }); this.lastCurlCommand = this.generateCurlCommand(config); if (this.mode === types_1.Mode.DEVELOPMENT) { console.log('Request Curl Command:', this.lastCurlCommand); } return config; }); this.client.interceptors.response.use((response) => { if (this.mode === types_1.Mode.DEVELOPMENT) { console.log('Response Status:', response.status); } return { ...response, data: { data: response.data, curlCommand: this.lastCurlCommand } }; }, error => this.handleAxiosError(error)); } /** * @method get * @description Performs a GET request to the specified URL with retry mechanism * @template T - The expected response data type * @param {string} url - The endpoint URL * @param {Record<string, any>} [params] - Optional query parameters * @param {AxiosRequestConfig} [config] - Optional Axios request configuration * @returns {Promise<ApiResponse<T>>} The response data with curl command */ async get(url, params, config) { return this.executeWithRetry(async () => { const response = await this.client.get(url, { params, ...config }); return response.data; }, `GET ${url}`); } /** * @method post * @description Performs a POST request to the specified URL with retry mechanism * @template T - The expected response data type * @param {string} url - The endpoint URL * @param {any} [data] - The request body * @param {AxiosRequestConfig} [config] - Optional Axios request configuration * @returns {Promise<ApiResponse<T>>} The response data with curl command */ async post(url, data, config) { return this.executeWithRetry(async () => { const response = await this.client.post(url, data, config); return response.data; }, `POST ${url}`); } /** * @method put * @description Performs a PUT request to the specified URL with retry mechanism * @template T - The expected response data type * @param {string} url - The endpoint URL * @param {any} [data] - The request body * @param {AxiosRequestConfig} [config] - Optional Axios request configuration * @returns {Promise<ApiResponse<T>>} The response data with curl command */ async put(url, data, config) { return this.executeWithRetry(async () => { const response = await this.client.put(url, data, config); return response.data; }, `PUT ${url}`); } /** * @method delete * @description Performs a DELETE request to the specified URL with retry mechanism * @template T - The expected response data type * @param {string} url - The endpoint URL * @param {any} [data] - Optional request body * @param {AxiosRequestConfig} [config] - Optional Axios request configuration * @returns {Promise<ApiResponse<T>>} The response data with curl command */ async delete(url, data, config) { return this.executeWithRetry(async () => { const response = await this.client.delete(url, { data, ...config }); return response.data; }, `DELETE ${url}`); } } exports.ApiClient = ApiClient;