topkat-utils
Version:
A comprehensive collection of TypeScript/JavaScript utility functions for common programming tasks. Includes validation, object manipulation, date handling, string formatting, and more. Zero dependencies, fully typed, and optimized for performance.
214 lines • 10.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DescriptiveError = exports.failSafe = exports.tryCatch = exports.err422IfNotSet = exports.errXXXIfNotSet = exports.err500IfEmptyOrNotSet = exports.errIfEmptyOrNotSet = exports.err500IfNotSet = exports.errIfNotSet = void 0;
//----------------------------------------
// ERROR UTILS
//----------------------------------------
const config_1 = require("./config");
const isset_1 = require("./isset");
const is_empty_1 = require("./is-empty");
const clean_stack_trace_1 = require("./clean-stack-trace");
const logger_utils_1 = require("./logger-utils");
const remove_circular_json_stringify_1 = require("./remove-circular-json-stringify");
const string_utils_1 = require("./string-utils");
const is_object_1 = require("./is-object");
function errIfNotSet(objOfVarNamesWithValues) { return errXXXIfNotSet(422, false, objOfVarNamesWithValues); }
exports.errIfNotSet = errIfNotSet;
function err500IfNotSet(objOfVarNamesWithValues) { return errXXXIfNotSet(500, false, objOfVarNamesWithValues); }
exports.err500IfNotSet = err500IfNotSet;
function errIfEmptyOrNotSet(objOfVarNamesWithValues) { return errXXXIfNotSet(422, true, objOfVarNamesWithValues); }
exports.errIfEmptyOrNotSet = errIfEmptyOrNotSet;
function err500IfEmptyOrNotSet(objOfVarNamesWithValues) { return errXXXIfNotSet(500, true, objOfVarNamesWithValues); }
exports.err500IfEmptyOrNotSet = err500IfEmptyOrNotSet;
function errXXXIfNotSet(errCode, checkEmpty, objOfVarNamesWithValues) {
const missingVars = [];
for (const prop in objOfVarNamesWithValues) {
if (!(0, isset_1.isset)(objOfVarNamesWithValues[prop]) || (checkEmpty && (0, is_empty_1.isEmpty)(objOfVarNamesWithValues[prop])))
missingVars.push(prop);
}
if (missingVars.length)
throw new DescriptiveError(`requiredVariableEmptyOrNotSet`, { code: errCode, origin: 'Validator', varNames: missingVars.join(', ') });
}
exports.errXXXIfNotSet = errXXXIfNotSet;
function err422IfNotSet(o) {
const m = [];
for (const p in o)
if (!(0, isset_1.isset)(o[p]))
m.push(p);
if (m.length)
throw new DescriptiveError(`requiredVariableEmptyOrNotSet`, { code: 422, origin: 'Validator', varNames: m.join(', ') });
}
exports.err422IfNotSet = err422IfNotSet;
/** Works natively with sync AND async functions */
function tryCatch(callback, onErr = () => { }) {
try {
const result = callback();
if (result instanceof Promise)
return result.catch(e => onErr(e));
else
return result;
}
catch (err) {
return onErr(err);
}
}
exports.tryCatch = tryCatch;
exports.failSafe = tryCatch; // ALIAS
function extraInfosRendererDefault(extraInfos) {
return [
'== EXTRA INFOS ==',
(0, remove_circular_json_stringify_1.removeCircularJSONstringify)({ ...extraInfos, message: undefined, stack: undefined, originalError: undefined, hasBeenLogged: undefined, logs: undefined }, 2)
];
}
class DescriptiveError extends Error {
/** Full error infos, extra infos + message and code...etc as object */
errorDescription = {};
/** The parent error if any */
originalError = {};
/** used to uniquely identify the error */
id = (0, string_utils_1.generateToken)(24, true);
/** Http code. Eg: 404, 403... */
code;
message;
options;
/** Logging of the error is async, unless disabled, so that it wait one frame to allow to log it manually */
hasBeenLogged = false;
isAxiosError = false;
doNotLog = false; // just an alias for the above, actually using this one can be more readable in some situations
logs = [];
isDescriptiveError = true;
/** This for client usage, and is not used by DescriptiveError. It can be used to mark an error as handled by your system. */
isHandled = false;
constructor(message, options = {}) {
super(message);
delete options.errMsgId;
this.message = message;
this.isAxiosError = (options?.err?.stack || options.stack)?.startsWith('Axios') || false;
const { doNotWaitOneFrameForLog = options.code === 500, ...optionsClean } = options;
this.options = optionsClean;
if (optionsClean.err) {
// ORIGINAL ERROR
if (typeof optionsClean.err !== 'string')
optionsClean.err.hasBeenLogged = true;
// get props from prototype to be in actual error object for further manipulation
optionsClean.err = { message: optionsClean.err.message, stack: optionsClean.err.stack, ...optionsClean.err };
}
this.parseError(); // make sure to parse it before any log or reuse
this.hasBeenLogged = false;
if (doNotWaitOneFrameForLog)
this.log();
else
setTimeout(() => {
// wait one event loop because it can be catched in a parent module
// and it can be logged manually sometimes
if (!this.hasBeenLogged)
this.log();
});
const { onError } = (0, config_1.configFn)();
if (typeof onError === 'function')
onError(message, options);
}
/** Compute extraInfos and parse options */
parseError(forCli = false) {
const errorLogs = [];
const { err, noStackTrace = false, ressource, extraInfosRenderer = extraInfosRendererDefault, maskForFront, ...extraInfosRaw } = this.options;
let { code } = this.options;
const extraInfos = {
id: this.id,
...extraInfosRaw,
// additionnal extra info passed from parent error
...(this.options.extraInfos || {}),
};
this.code = code || 500;
if (this.options.doNotDisplayCode || (this.options.hasOwnProperty('code') && !(0, isset_1.isset)(this.options.code)))
delete this.code;
if (!(0, isset_1.isset)(extraInfos.value) && this.options.hasOwnProperty('value'))
extraInfos.value = 'undefined';
if (!(0, isset_1.isset)(extraInfos.gotValue) && this.options.hasOwnProperty('gotValue'))
extraInfos.gotValue = 'undefined';
this.isAxiosError = this.isAxiosError || (extraInfos?.err?.stack || extraInfos.stack || this.stack)?.startsWith('Axios') || false;
if (this.isAxiosError) {
// trying to extract response
extraInfos.responseData = err && 'response' in err ? err.response.data : extraInfos.response?.data;
}
if ((0, isset_1.isset)(ressource)) {
code = 404;
if (this.message === '404')
this.message = `Ressource ${ressource} not found`;
extraInfos.ressource = ressource;
}
errorLogs.push(computeErrorMessage(this));
const extraInfosForLogs = { ...extraInfos, ...((0, is_object_1.isObject)(maskForFront) ? maskForFront : { maskForFront }) };
if (Object.keys(extraInfosForLogs).length > 0) {
errorLogs.push(...extraInfosRenderer(extraInfosForLogs));
}
if (err) {
// actually, passing by there mean THE ERROR HAS BEEN CATCHED
this.originalError = err;
errorLogs.push('== ORIGINAL ERROR ==');
errorLogs.push(computeErrorMessage(err));
if (typeof err.parseError === 'function' || Array.isArray(err?.logs)) {
// The catched error is a DescriptiveError so from
// there we prevent further logs/ outpus from error
err.hasBeenLogged = true; // this will be logged in the child error so we dont want it to be logged twice
err.doNotLog = true;
const logFromOtherErr = err?.logs || err?.parseError?.(forCli) || [];
const [, ...errToLog] = logFromOtherErr;
errorLogs.push(...errToLog);
}
else {
errorLogs.push((0, remove_circular_json_stringify_1.removeCircularJSONstringify)({ ...err, hasBeenLogged: undefined }));
if (!noStackTrace && err.stack)
errorLogs.push((0, clean_stack_trace_1.cleanStackTrace)(err.stack));
if (err.extraInfos)
errorLogs.push((0, remove_circular_json_stringify_1.removeCircularJSONstringify)(err.extraInfos));
}
}
else {
if (!noStackTrace) {
const stackTranceClean = (0, clean_stack_trace_1.cleanStackTrace)(extraInfosRaw.stack || this.stack);
errorLogs.push(forCli ? logger_utils_1.C.dim(stackTranceClean) : stackTranceClean);
}
}
// THIS is used to access error as object
this.code = code || 500;
if (this.options.doNotDisplayCode || (this.options.hasOwnProperty('code') && !(0, isset_1.isset)(this.options.code)))
delete this.code;
this.errorDescription = {
id: this.id,
message: this.message,
code,
ressource,
originalError: err,
maskForFront,
...extraInfos,
};
this.logs = errorLogs;
return errorLogs;
}
log() {
this.parseError(); // re parse it in case it has been updated from the outside (eg: adding extraInfos)
const err = new Error();
err.message = this.logs.join('\n');
err.stack = (0, clean_stack_trace_1.cleanStackTrace)(this.stack);
if (this.hasBeenLogged === false && this.doNotLog === false) {
// Do not log "this" since in C.error this.log will get called.
// This has the advantage of parsing logs when logging a
// C.error(DescriptiveError) (calling err.log())
// And prevent an infinite loop
logger_utils_1.C.error(err);
}
this.hasBeenLogged = true;
}
toString() {
return this.logs.join('\n');
}
toJSON() {
return this.toString();
}
}
exports.DescriptiveError = DescriptiveError;
function computeErrorMessage(err) {
return (err.code ? err.code + ' ' : '') + (err.msg || err.message);
}
//# sourceMappingURL=error-utils.js.map