errlop
Version:
An extended Error class that envelops a parent error, such that the stack trace contains the causation
144 lines (143 loc) • 4.93 kB
JavaScript
"use strict";
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Convert to a valid number or null.
* @param input
*/
function getNumber(input) {
if (input == null || input === '')
return null;
const number = Number(input);
if (isNaN(number))
return null;
return number;
}
/**
* Fetch the exit code from the value
* @param value
*/
function getExitCode(value) {
if (value != null) {
if (typeof value.exitCode !== 'undefined')
return getNumber(value.exitCode);
if (typeof value.errno !== 'undefined')
return getNumber(value.errno);
if (typeof value.code !== 'undefined')
return getNumber(value.code);
}
return null;
}
/**
* Prepend a code to the stack if applicable.
* @param code
* @param stack
*/
function prependCode(code, stack) {
if (code && typeof code === 'string' && stack.includes(code) === false)
return `[${code}]: ${stack}`;
return stack;
}
/** Errlop, an extended Error class that envelops a parent Error to provide ancestry stack inforation. */
class Errlop extends Error {
/**
* Turn the input and parent into an Errlop instance.
* @param input
* @param parent
*/
constructor(input, parent = null) {
var _a, _b;
if (!input)
throw new Error('Attempted to create an Errlop without an input');
// construct with message
super(input.message || input);
// implements
this.parent = null;
this.ancestors = [];
this.exitCode = null;
this.orphanStack = '';
this.code = '';
this.level = '';
/** Duck typing so native classes can work with transpiled classes, as otherwise they would fail instanceof checks. */
this.klass = Errlop;
// parent
// if override not set, fallback to parent or cause
if (!parent)
parent = input.parent || input.cause;
// if override, or parent/cause was set, then set this.parent
if (parent) {
if (Errlop.isError(parent)) {
this.parent = parent;
}
else {
this.parent = new Errlop(parent);
}
}
// ancestors, assumed to only exist on Errlop
if (this.parent) {
this.ancestors.push(this.parent);
if (Errlop.isErrlop(this.parent)) {
this.ancestors.push(...this.parent.ancestors);
}
}
// exitCode, code, level
for (const error of [input, this, ...this.ancestors]) {
if (this.exitCode == null) {
this.exitCode = getExitCode(error);
}
if (this.code === '' && error.code != null && error.code !== '') {
this.code = (_a = getNumber(error.code)) !== null && _a !== void 0 ? _a : error.code.toString();
}
if (this.level === '' && error.level != null && error.level !== '') {
this.level = (_b = getNumber(error.level)) !== null && _b !== void 0 ? _b : error.level.toString();
}
}
// orphanStack
this.orphanStack = prependCode(this.code, (input.orphanStack ||
input.stack ||
this.stack ||
// this.stack should exist, unless something that extended Errlop broke it
this.message ||
this ||
'').toString());
// stack
this.stack = [this, ...this.ancestors]
.map(function (error) {
// error is either Errlop or Error, however that doesn't stop extenders from breaking them
return prependCode(error.code, (error.orphanStack ||
error.stack ||
// those should exist, unless something that extended Error broke it
error.message ||
error ||
'').toString());
})
.filter((s) => Boolean(s)) // filter out Error instances that have no stack
.join(Errlop.stackSeparator);
}
static isErrlop(value) {
return value && (value instanceof this || value.klass === this);
}
static isError(value) {
return value instanceof Error || Errlop.isErrlop(value);
}
/**
* Ensure that the value is an Errlop instance
* @param value
*/
static ensure(value) {
return this.isErrlop(value) ? value : this.create(value, null);
}
/**
* Syntactic sugar for Errlop class creation.
* Enables `Errlop.create(...)` to achieve `new Errlop(...)`
* @param input
* @param parent
*/
static create(input, parent = null) {
return new this(input, parent);
}
}
// static methods
/** The separator to use for the stack entries */
Errlop.stackSeparator = '\n↳ ';
exports.default = Errlop;