UNPKG

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
"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