@getanthill/datastore
Version:
Event-Sourced Datastore
163 lines • 5.83 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const axios_1 = __importDefault(require("axios"));
const qs_1 = __importDefault(require("qs"));
const merge_1 = __importDefault(require("lodash/merge"));
class Core extends events_1.EventEmitter {
constructor(config = {}) {
super();
this._config = {
baseUrl: 'http://localhost:3001',
timeout: 10000,
token: 'token',
debug: false,
retriableMethods: Core.DEFAULT_RETRIABLE_METHODS,
retriableErrors: Core.DEFAULT_RETRIABLE_ERRORS,
maxRetry: Core.DEFAULT_MAX_RETRY,
};
this._telemetry = null;
this._config = (0, merge_1.default)({}, this._config, config);
this._axios = axios_1.default.create({
baseURL: this._config.baseUrl,
timeout: this._config.timeout,
withCredentials: true,
headers: {
...Core.DEFAULT_HEADERS,
Authorization: this._config.token,
},
paramsSerializer: {
serialize: Core.paramsSerializer,
},
});
// @ts-ignore
this._axios.interceptors.response.use((response) => response, this.responseInterceptor.bind(this));
if (this._config.telemetry) {
this._telemetry = this._config.telemetry;
this._recordHttpRequestDuration =
this._telemetry?.metrics?.createHistogram('datastore_core_request_duration_ms', // name
'Duration of the Datastore Core SDK HTTP requests in ms', // description
[1, 2, 3, 5, 10, 25, 50, 100, 250, 1000], // buckets
'ms');
}
}
static paramsSerializer(params) {
const p = params;
if ('_q' in p) {
p._q = JSON.stringify(p._q);
}
return ('q=' +
encodeURIComponent(JSON.stringify(params)) +
'&' +
qs_1.default.stringify(p, { arrayFormat: 'brackets' }));
}
cloneAxiosError(err) {
const _err = new Error(err.message);
_err.name = err.name;
// @ts-ignore
_err.code = err.code;
// @ts-ignore
_err.config = err.config;
// @ts-ignore
_err.response = err.response;
// @ts-ignore
_err.response && (_err.response.request = undefined);
return _err;
}
getToken() {
return this._config.token;
}
setTimeout(timeout) {
this._axios.defaults.timeout = timeout;
}
getPath(...fragments) {
return '/' + ['api', ...fragments].join('/');
}
inspect(obj) {
if (this._config.debug === true) {
console.log(require('util').inspect(obj, false, null, true));
}
return obj;
}
async request(config) {
const [domain] = config.url?.substring(5).split('/', 1);
const tic = Date.now();
try {
this._telemetry?.metric('datastore_core_request_total', {
domain,
state: 'request',
method: config.method,
});
const res = await this._axios.request(config);
this._recordHttpRequestDuration?.(Date.now() - tic, {
domain,
state: 'success',
method: config.method,
});
this._telemetry?.metric('datastore_core_request_total', {
domain,
state: 'success',
method: config.method,
});
return res;
}
catch (err) {
this._recordHttpRequestDuration?.(Date.now() - tic, {
domain,
state: 'error',
method: config.method,
status: err.response?.status,
});
this._telemetry?.metric('datastore_request', {
domain,
state: 'error',
method: config.method,
status: err.response?.status,
});
const _err = this.cloneAxiosError(err);
this._telemetry?.logger?.warn('[sdk.core#request] Error', { err: _err });
this.inspect(_err);
throw _err;
}
}
/**
* @warn this interceptor is here to handle race conditions
* happening in Node.js 20 with `keepAlive` enabled with parallel
* requests.
*
* @see https://github.com/node-fetch/node-fetch/issues/1735
*/
responseInterceptor(error) {
const originalRequest = error.config;
if (originalRequest === undefined) {
throw error;
}
// @ts-ignore
const retry = originalRequest._retry ?? 0;
const originalRequestMethod = originalRequest.method ?? 'get';
if (retry < this._config.maxRetry &&
this._config.retriableErrors.includes(error.message) &&
this._config.retriableMethods.includes(originalRequestMethod)) {
if (typeof originalRequest.params?._q === 'string') {
originalRequest.params._q = JSON.parse(originalRequest.params._q);
}
// @ts-ignore
originalRequest._retry = retry + 1;
return this._axios(originalRequest);
}
throw error;
}
}
Core.ERRORS = {};
Core.DEFAULT_RETRIABLE_METHODS = ['get', 'head', 'options'];
Core.DEFAULT_RETRIABLE_ERRORS = ['socket hang up'];
Core.DEFAULT_MAX_RETRY = 3;
Core.DEFAULT_HEADERS = Object.freeze({
'Content-Type': 'application/json',
Accept: 'application/json',
});
exports.default = Core;
//# sourceMappingURL=Core.js.map