UNPKG

@reggieofarrell/axios-retry-client

Version:

A class based api client for both the server and browser built on `axios` and `axios-retry`, written in TypeScript

287 lines (286 loc) 12.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ApiResponseError = exports.AxiosRetryClient = exports.RequestType = void 0; const axios_1 = __importDefault(require("axios")); const axios_retry_1 = __importDefault(require("axios-retry")); const logger_1 = require("./logger"); var RequestType; (function (RequestType) { RequestType["GET"] = "GET"; RequestType["POST"] = "POST"; RequestType["PUT"] = "PUT"; RequestType["PATCH"] = "PATCH"; RequestType["DELETE"] = "DELETE"; })(RequestType || (exports.RequestType = RequestType = {})); class AxiosRetryClient { constructor(config) { var _a, _b; const backoff = ((_a = config.retryConfig) === null || _a === void 0 ? void 0 : _a.backoff) || 'exponential'; const delayFactor = ((_b = config.retryConfig) === null || _b === void 0 ? void 0 : _b.delayFactor) || 500; const name = config.name || 'AxiosRetryClient'; const defaultRetryConfig = { retries: 0, retryDelay: (retryCount, error) => this.getRetryDelay(retryCount, error, backoff, delayFactor), onRetry: (retryCount, error, requestConfig) => { if (this.debug) { console.log(`[${name}] Retry #${retryCount} for ${requestConfig.baseURL}${requestConfig.url} due to error: ${error.message}`); } }, delayFactor, backoff, }; const retryConfig = config.retryConfig ? Object.assign(Object.assign({}, defaultRetryConfig), config.retryConfig) : defaultRetryConfig; delete config.retryConfig; config = Object.assign({ axiosConfig: {}, retryConfig, debug: false, debugLevel: 'normal', name }, config); this.axiosConfig = config.axiosConfig; this.axiosRetry = axios_retry_1.default; this.baseURL = config.baseURL; this.debug = config.debug; this.debugLevel = config.debugLevel; this.name = config.name; this.retryConfig = config.retryConfig; const client = axios_1.default.create(Object.assign(Object.assign({}, config.axiosConfig), { baseURL: config.baseURL })); (0, axios_retry_1.default)(client, config.retryConfig); this.axios = client; } getRetryDelay(retryCount, error, backoff, delayFactor) { if (backoff === 'exponential') { return axios_retry_1.default.exponentialDelay(retryCount, error, delayFactor); } else if (backoff === 'linear') { return axios_retry_1.default.linearDelay(delayFactor)(retryCount, error); } else { return delayFactor; } } async _request(requestType, url, data, config = {}) { var _a, _b; let req; if (config.retryConfig) { let retryConfig; if (config.retryConfig.backoff || config.retryConfig.delayFactor) { retryConfig = Object.assign(Object.assign(Object.assign({}, this.retryConfig), { retryDelay: (retryCount, error) => { var _a, _b; return this.getRetryDelay(retryCount, error, ((_a = config.retryConfig) === null || _a === void 0 ? void 0 : _a.backoff) || this.retryConfig.backoff, ((_b = config.retryConfig) === null || _b === void 0 ? void 0 : _b.delayFactor) || this.retryConfig.delayFactor); } }), config.retryConfig); } else { retryConfig = Object.assign(Object.assign({}, this.retryConfig), config.retryConfig); } config['axios-retry'] = retryConfig; } // Call beforeRequest hook to potentially modify the request parameters const filteredArgs = await this.preRequestFilter(requestType, url, data, config); data = (_a = filteredArgs.data) !== null && _a !== void 0 ? _a : data; config = (_b = filteredArgs.config) !== null && _b !== void 0 ? _b : config; // Call beforeRequestAction hook to perform any actions before the request is sent await this.preRequestAction(requestType, url, data, config); try { switch (requestType) { case RequestType.GET: req = await this.axios.get(url, config); break; case RequestType.POST: req = await this.axios.post(url, data, config); break; case RequestType.PUT: req = await this.axios.put(url, data, config); break; case RequestType.PATCH: req = await this.axios.patch(url, data, config); break; case RequestType.DELETE: req = await this.axios.delete(url, config); break; } } catch (error) { this.errorHandler(error, requestType, url); } return { request: req, data: req.data }; } async get(url, config = {}) { return this._request(RequestType.GET, url, undefined, config); } async post(url, data, config = {}) { return this._request(RequestType.POST, url, data, config); } async put(url, data, config = {}) { return this._request(RequestType.PUT, url, data, config); } async patch(url, data, config = {}) { return this._request(RequestType.PATCH, url, data, config); } async delete(url, config = {}) { return this._request(RequestType.DELETE, url, undefined, config); } /** * Override this method in your extending class to modify the request data or * config before the request is sent. * * @deprecated Use preRequestFilter instead. This will be removed in a future version. * @param requestType - The request type (GET, POST, PUT, PATCH, DELETE) * @param url - The request URL * @param data - The request data * @param config - The request config * @returns The modified request parameters */ async beforeRequestFilter(requestType, url, data, config) { return this.preRequestFilter(requestType, url, data, config); } /** * Define this requestType in your extending class to globally modify the * request data or config before the request is sent. * * @param requestType - The request type (GET, POST, PUT, PATCH, DELETE) * @param url - The request URL * @param data - The request data * @param config - The request config * @returns The modified request parameters */ async preRequestFilter( // @ts-expect-error - not used here, but may be used in a subclass requestType, // @ts-expect-error - not used here, but may be used in a subclass url, data, config) { return { data, config }; } /** * Override this method in your extending class to perform any actions before * the request is sent such as logging the request details. By default, this will * log the request details if debug is enabled. * * @deprecated Use preRequestAction instead. This will be removed in a future version. * @param requestType - The request type (GET, POST, PUT, PATCH, DELETE) * @param url - The request URL * @param data - The request data * @param config - The request config */ async beforeRequestAction(requestType, url, data, config) { return this.preRequestAction(requestType, url, data, config); } /** * Override this method in your extending class to perform any actions before * the request is sent such as logging the request details. By default, this will * log the request details if debug is enabled. * @param requestType - The request type (GET, POST, PUT, PATCH, DELETE) * @param url - The request URL * @param data - The request data * @param config - The request config */ async preRequestAction(requestType, url, data, config) { if (this.debug) { if (this.debugLevel === 'verbose') { (0, logger_1.logData)(`[${this.name}] ${requestType} ${url}`, { data, config }); } else { (0, logger_1.logData)(`[${this.name}] ${requestType} ${url}`, { data }); } } } /** * Handles errors from the axios instance. Override this method for * custom error handling functionality specific to the API you are * consuming. * @param error - The error object * @param reqType - The request type * @param url - The request URL * @see https://axios-http.com/docs/handling_errors */ errorHandler(error, reqType, url) { var _a, _b; if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx if (this.debug) { if (this.debugLevel === 'verbose') { (0, logger_1.logData)(`[${this.name}] ${reqType} ${url} : error.response`, error.response); } else { (0, logger_1.logData)(`[${this.name}] ${reqType} ${url} : error.response.data`, error.response.data); } } if (error.response.data && error.response.status && ((_a = error.response.data) === null || _a === void 0 ? void 0 : _a.message)) { throw new ApiResponseError(`[${this.name}] ${reqType} ${url} : [${error.response.status}] ${error.response.data.message}`, error.response.status, error.response.data, error.toString ? error.toString() : error); } else if (error.response && error.response.status && error.response.data && !((_b = error.response.data) === null || _b === void 0 ? void 0 : _b.message)) { throw new ApiResponseError(`[${this.name}] ${reqType} ${url} : [${error.response.status}]`, error.response.status, error.response.data, error.toString ? error.toString() : error); } else { throw new Error(error); } } else { this.handleResponseNotReceivedOrOtherError(error, reqType, url); } } /** * Handles errors where a response is not received or other errors occur * @param error - The error object * @param reqType - The request type * @param url - The request URL */ handleResponseNotReceivedOrOtherError(error, reqType, url) { if (error.request) { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js if (this.debug) { if (this.debugLevel === 'verbose') { (0, logger_1.logData)(`${this.name}] ${reqType} ${url}: error.config`, error.config); } (0, logger_1.logData)(`[${this.name}] ${reqType} ${url} : error.request`, error.request); } throw new Error(`[${this.name}] ${reqType} ${url} [no response] : ${error.message}`, { cause: error, }); } else { // Something happened in setting up the request that triggered an Error if (this.debug) { if (this.debugLevel === 'verbose') { (0, logger_1.logData)(`[${this.name}] ${reqType} ${url} : error`, error); } else { console.log(`[${this.name}] ${reqType} ${url} error.message : ${error.message}`); } } if (error.message) { throw new Error(`[${this.name}] ${reqType} ${url} : ${error.message}`, { cause: error, }); } else { throw new Error(error); } } } } exports.AxiosRetryClient = AxiosRetryClient; /** * Base class for API errors. * @extends Error */ class ApiResponseError extends Error { /** * Creates an instance of ApiError. * @param {string} message - The error message. * @param {number} status - The HTTP status code. * @param {object|string} response - The response. * @param {any} cause - The cause of the error. */ constructor(message, status, response, cause) { super(message, { cause }); this.status = status; this.response = response; } } exports.ApiResponseError = ApiResponseError;