@shirudo/base-error
Version:
A simple error base class for TypeScript
255 lines (251 loc) • 9.79 kB
JavaScript
'use strict';
var __typeError = (msg) => {
throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
// src/BaseError.ts
var _BaseError_instances, setCause_fn, serializeCause_fn, serializeCircularObject_fn, _BaseError_static, hasNativeCauseSupport_fn, captureStack_fn, filterInternalFrames_fn;
var _BaseError = class _BaseError extends Error {
/**
* Creates a new BaseError instance with automatic name inference.
*
* @param message – Human-readable explanation (name will be inferred from constructor)
* @param cause – Optional underlying error or extra context
*/
constructor(message, cause) {
super(message);
__privateAdd(this, _BaseError_instances);
/** Epoch-ms timestamp (numeric) */
this.timestamp = Date.now();
/** ISO-8601 timestamp (string) for log aggregators that prefer text */
this.timestampIso = (/* @__PURE__ */ new Date()).toISOString();
this._localizedMessages = /* @__PURE__ */ new Map();
this.name = this.constructor.name;
if (cause !== void 0) {
__privateMethod(this, _BaseError_instances, setCause_fn).call(this, cause);
}
Object.setPrototypeOf(this, new.target.prototype);
this.stack = __privateMethod(this, _BaseError_instances, captureStack_fn).call(this);
}
// ————————————————————————————————————————————————————————————————
// Methods for User-Friendly Messages
// ————————————————————————————————————————————————————————————————
/**
* Sets the default user-friendly message.
* This is used as a fallback when a specific localization is not available.
* @param message The default user-friendly message (typically in English).
* @returns The error instance for chaining.
*/
withUserMessage(message) {
this._defaultUserMessage = message;
return this;
}
/**
* Adds a user-friendly message for a specific language.
* Throws an error if a message for the given language already exists.
* @param lang The language code (e.g., 'de', 'es', 'fr-CA').
* @param message The localized message.
* @returns The error instance for chaining.
* @throws Error if a message for the given language already exists.
*/
addLocalizedMessage(lang, message) {
if (this._localizedMessages.has(lang)) {
throw new Error(
`Localized message for language '${lang}' already exists. Use updateLocalizedMessage() to modify existing messages.`
);
}
this._localizedMessages.set(lang, message);
return this;
}
/**
* Updates or sets a user-friendly message for a specific language.
* This method allows overwriting existing messages for the same language.
* @param lang The language code (e.g., 'de', 'es', 'fr-CA').
* @param message The localized message.
* @returns The error instance for chaining.
*/
updateLocalizedMessage(lang, message) {
this._localizedMessages.set(lang, message);
return this;
}
/**
* Retrieves the most appropriate user-friendly message based on language preference.
* The fallback order is: preferred language -> fallback language -> default message.
* @param options - Language preference options.
* @returns The user-friendly message, or `undefined` if none is set.
*/
getUserMessage(options) {
const { preferredLang, fallbackLang } = options || {};
if (preferredLang && this._localizedMessages.has(preferredLang)) {
return this._localizedMessages.get(preferredLang);
}
if (fallbackLang && this._localizedMessages.has(fallbackLang)) {
return this._localizedMessages.get(fallbackLang);
}
return this._defaultUserMessage;
}
/** Serialises the error for JSON logs */
toJSON() {
const { name, message, timestamp, timestampIso, stack } = this;
const cause = this.cause;
const json = {
name,
message,
// The original technical message
timestamp,
timestampIso,
stack,
cause: __privateMethod(this, _BaseError_instances, serializeCause_fn).call(this, cause)
};
if (this._defaultUserMessage) {
json.userMessage = this._defaultUserMessage;
}
if (this._localizedMessages.size > 0) {
json.localizedMessages = Object.fromEntries(this._localizedMessages);
}
return json;
}
/** Readable one-liner plus optional nested cause. */
toString() {
const cause = this.cause;
return `[${this.name}] ${this.message}${cause ? `
Caused by: ${cause}` : ""}`;
}
};
_BaseError_instances = new WeakSet();
// ————————————————————————————————————————————————————————————————
// Internal helpers
// ————————————————————————————————————————————————————————————————
/**
* Sets the cause property using native support when available, with fallback.
* This provides better compatibility across different JavaScript environments.
*/
setCause_fn = function(cause) {
var _a;
if (__privateMethod(_a = _BaseError, _BaseError_static, hasNativeCauseSupport_fn).call(_a)) {
try {
Object.defineProperty(this, "cause", {
value: cause,
configurable: true,
writable: true,
enumerable: false
// Keep it non-enumerable like native cause
});
} catch {
this.cause = cause;
}
} else {
try {
Object.defineProperty(this, "cause", {
value: cause,
configurable: true,
writable: true,
enumerable: false
});
} catch {
this.cause = cause;
}
}
};
/**
* Intelligently serializes the cause for JSON output.
* Preserves stack traces and nested data instead of just toString().
*/
serializeCause_fn = function(cause) {
if (cause === void 0 || cause === null) {
return cause;
}
if (cause instanceof Error) {
return {
name: cause.name,
message: cause.message,
stack: cause.stack,
// Recursively serialize nested causes
cause: __privateMethod(this, _BaseError_instances, serializeCause_fn).call(this, cause.cause)
};
}
if (typeof cause === "object" && cause !== null) {
try {
return JSON.parse(JSON.stringify(cause));
} catch {
return __privateMethod(this, _BaseError_instances, serializeCircularObject_fn).call(this, cause);
}
}
return cause;
};
/**
* Creates a more useful representation of circular objects for debugging.
* Instead of just "[object Object]", it extracts key information.
*/
serializeCircularObject_fn = function(obj) {
const type = obj.constructor?.name || "Object";
const keys = Object.keys(obj).slice(0, 5);
const keyInfo = keys.length > 0 ? ` with keys: [${keys.join(", ")}]` : "";
const moreKeys = Object.keys(obj).length > 5 ? "..." : "";
return `[Circular ${type}${keyInfo}${moreKeys}]`;
};
_BaseError_static = new WeakSet();
hasNativeCauseSupport_fn = function() {
if (typeof process !== "undefined" && process.versions?.node) {
const [major, minor] = process.versions.node.split(".").map(Number);
return major > 16 || major === 16 && minor >= 9;
}
return typeof window !== "undefined" && "cause" in Error.prototype;
};
/**
* Captures and filters the stack trace without affecting global state.
* Filters out internal BaseError frames for cleaner stack traces.
*/
captureStack_fn = function() {
if (typeof Error.captureStackTrace === "function") {
Error.captureStackTrace(this, this.constructor);
return __privateMethod(this, _BaseError_instances, filterInternalFrames_fn).call(this, this.stack);
}
let tempStack;
try {
throw new Error();
} catch (e) {
tempStack = e.stack;
}
if (!tempStack) {
return void 0;
}
return __privateMethod(this, _BaseError_instances, filterInternalFrames_fn).call(this, tempStack);
};
/**
* Filters out internal BaseError frames and updates the error header.
* This provides cleaner stack traces by removing implementation details.
*/
filterInternalFrames_fn = function(stack) {
if (!stack) {
return void 0;
}
const lines = stack.split("\n");
const filteredLines = [];
filteredLines.push(`${this.name}: ${this.message}`);
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
if (line.includes("#captureStack") || line.includes("#filterInternalFrames") || line.includes("BaseError.constructor") || line.includes("new BaseError") || line.includes("captureStack_fn") || // Compiled private method name
line.includes("filterInternalFrames_fn") || // Compiled private method name
// Skip the temporary error creation frame
line.includes("Object.<anonymous>") && line.includes("captureStack")) {
continue;
}
filteredLines.push(line);
}
return filteredLines.join("\n");
};
__privateAdd(_BaseError, _BaseError_static);
var BaseError = _BaseError;
// src/utils/guard.ts
function guard(condition, error) {
if (!condition) {
throw error;
}
}
exports.BaseError = BaseError;
exports.guard = guard;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map