@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
JavaScript
;
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;