import CircuitBreaker from "opossum";
import Fetcher from "./fetcher";
import { getBreakerIndex } from "./utils";
/**
* Constructs a ResilientClient.
*
* @class Client
* @param {CircuitBreaker.Options} circuitBreakerOptions Options for the circuit breaker
* @param {AxiosRequestConfig} requestOptions Options for the {@link Fetcher}
*/
class Client {
constructor(circuitBreakerOptions, requestOptions) {
this._circuits = new Map();
this._cbOptions = circuitBreakerOptions;
this._fetcher = new Fetcher(requestOptions);
}
/**
* Perform a HTTP request that might fails
*
* @param {AxiosRequestConfig} fetchConfig Options for the {@link Fetcher}
* @return {Promise<AxiosResponse>} Promise resolves with the HTTP response
* on success or is rejected on failure of the action. Use isBreakerError()
* to determine if a rejection was a result of the circuit breaker or the
* HTTP call.
*/
request(fetchConfig) {
const { method, url } = fetchConfig;
if (method && url) {
let breaker;
const abortController = new AbortController();
const circuitBreakerOptions = Object.assign({ abortController }, this._cbOptions);
const circuitIndexObject = {
requestMethod: method,
requestURL: url,
};
const circuitIndexString = getBreakerIndex(circuitIndexObject);
if (this._circuits.has(circuitIndexString)) {
breaker = this._circuits.get(circuitIndexString);
}
else {
breaker = new CircuitBreaker(this._fetcher.exec, circuitBreakerOptions);
this._circuits.set(circuitIndexString, breaker);
}
return breaker.fire(circuitBreakerOptions.abortController.signal, fetchConfig);
}
else {
throw new Error("Method and URL are required in request config");
}
}
/**
* Returns true if the provided error was generated by an circuit breaker. It will be false
* if the error came from the action itself.
*
* @param {Error} error The Error to check
* @returns {Boolean} true if the error was generated by an circuit breaker
*/
static isBreakerError(error) {
return CircuitBreaker.isOurError(error);
}
/**
* Get an specific circuit breaker.
* Helpful when you need to get stats from a circuit breaker.
*
* @param {BreakerIndexObject} indexObject IndexObject contains the URL and method of the request
* associated with a circuit breaker. Request and URL together are used as index for the circuit breaker.
* They can be formatted into an string index using {@link BreakerIndexObject}
* @returns {CircuitBreaker} Circuit breaker
*/
getCircuitBreaker(indexObject) {
const index = getBreakerIndex(indexObject);
const breaker = this._circuits.get(index);
if (breaker) {
return breaker;
}
else {
throw new Error(`There is no circuit breaker with index ${index}`);
}
}
}
export default Client;