UNPKG

@ayonli/jsext

Version:

A JavaScript extension package for building strong and modern applications.

362 lines (358 loc) 11.5 kB
'use strict'; var object = require('./object.js'); var error_Exception = require('./error/Exception.js'); var error_common = require('./error/common.js'); var event = require('./event.js'); var _class = require('./class.js'); /** * Functions for converting errors to/from other types of objects. * @module */ const { NotSupportedError } = error_common; const errorTypeRegistry = new Map([ ["Exception", error_Exception.default] ]); /** * Registers an error constructor that can be used by {@link fromObject} to * reverse a plain object which is previously transformed by {@link toObject} * back to an error instance. * */ function registerErrorType(ctor) { errorTypeRegistry.set(ctor.name, ctor); } /** * Returns the error constructor by the `name`. * @inner */ function getErrorConstructor(name) { let type = errorTypeRegistry.get(name); if (!type && name in globalThis) { const value = globalThis[name]; if (value === Error || _class.isSubclassOf(value, Error) || (typeof DOMException === "function" && value === DOMException)) { type = value; } } return type ? type : null; } const commonErrors = Object.values(error_common) .filter(value => _class.isSubclassOf(value, Error)); commonErrors.forEach(ctor => registerErrorType(ctor)); /** * Transforms the error to a plain object so that it can be serialized to JSON * and later reversed back to an error instance using {@link fromObject}. * * @example * ```ts * import { toObject } from "@ayonli/jsext/error"; * * const err = new Error("Something went wrong."); * * const obj = toObject(err); * console.log(obj); * // { * // "@@type": "Error", * // name: "Error", * // message: "Something went wrong.", * // stack: "Error: Something went wrong.\n at <anonymous>:1:13" * // } * ``` */ function toObject(err) { if (!(err instanceof Error) && err["name"] && err["message"]) { // Error-like err = fromObject(err, Error); } const obj = { "@@type": err.constructor.name, ...object.omit(err, ["toString", "toJSON", "__callSiteEvals"]), }; if ("cause" in obj) { obj["cause"] = obj["cause"] instanceof Error ? toObject(obj["cause"]) : obj["cause"]; } if (obj["@@type"] === "AggregateError" && Array.isArray(obj["errors"])) { obj["errors"] = obj["errors"].map(item => { return item instanceof Error ? toObject(item) : item; }); } return obj; } function fromObject(obj, ctor = null, strict = false) { var _a, _b, _c, _d; // @ts-ignore if (!(obj === null || obj === void 0 ? void 0 : obj.name) || (strict && !obj["@@type"])) { return null; } // @ts-ignore const typeName = obj["@@type"] || obj.name; // @ts-ignore ctor !== null && ctor !== void 0 ? ctor : (ctor = ((_a = getErrorConstructor(typeName)) !== null && _a !== void 0 ? _a : Error)); let err; if (ctor.name === "DOMException" && typeof DOMException === "function") { err = new ctor((_b = obj["message"]) !== null && _b !== void 0 ? _b : "", obj["name"]); } else { err = Object.create(ctor.prototype, { message: { configurable: true, enumerable: false, writable: true, value: (_c = obj["message"]) !== null && _c !== void 0 ? _c : "", }, }); Object.defineProperty(err, "name", { configurable: true, enumerable: false, writable: true, value: obj["name"], }); } if (obj["stack"] !== undefined) { Object.defineProperty(err, "stack", { configurable: true, enumerable: false, writable: true, value: obj["stack"], }); } if (obj["cause"] != undefined) { Object.defineProperty(err, "cause", { configurable: true, enumerable: false, writable: true, value: object.isPlainObject(obj["cause"]) ? ((_d = fromObject(obj["cause"], undefined, true)) !== null && _d !== void 0 ? _d : obj["cause"]) : obj["cause"], }); } const otherKeys = Reflect.ownKeys(obj).filter(key => ![ "@@type", "name", "message", "stack", "cause" ].includes(key)); otherKeys.forEach(key => { var _a; // @ts-ignore (_a = err[key]) !== null && _a !== void 0 ? _a : (err[key] = obj[key]); }); // @ts-ignore if (isAggregateError(err) && Array.isArray(err["errors"])) { err["errors"] = err["errors"].map(item => { return object.isPlainObject(item) ? fromObject(item) : item; }); } return err; } /** * Creates an `ErrorEvent` instance based on the given error. * * @example * ```ts * import { toErrorEvent } from "@ayonli/jsext/error"; * * const err = new Error("Something went wrong."); * * const event = toErrorEvent(err); * console.log(event); * // ErrorEvent { * // error: Error: Something went wrong. * // at <anonymous>:1:13, * // message: "Something went wrong.", * // filename: "", * // lineno: 1, * // colno: 13 * // } * ``` */ function toErrorEvent(err, type = "error") { let filename = ""; let lineno = 0; let colno = 0; if (err.stack) { const lines = err.stack.split("\n").map(line => line.trim()); let callSite = lines.find(line => line.startsWith("at ")); if (callSite) { callSite = callSite.slice(3); } else if (callSite = lines.find(line => line.includes("@") && line.length > 1)) { callSite = callSite.slice(callSite.indexOf("@") + 1); } if (callSite) { let start = callSite.lastIndexOf("("); let end = 0; if (start !== -1) { start += 1; end = callSite.indexOf(")", start); callSite = callSite.slice(start, end); } const matches = callSite.match(/:(\d+):(\d+)$/); if (matches) { filename = callSite.slice(0, matches.index); lineno = parseInt(matches[1]); colno = parseInt(matches[2]); } } } return event.createErrorEvent(type, { error: err, message: err.message, filename, lineno, colno, }); } /** * Creates an error instance based on the given `ErrorEvent` instance. * * @example * ```ts * import { fromErrorEvent } from "@ayonli/jsext/error"; * * const event = new ErrorEvent("error", { * message: "Something went wrong.", * filename: "", * lineno: 1, * colno: 13, * }); * * const err = fromErrorEvent(event); * console.log(err); * // Error: Something went wrong. * // at <anonymous>:1:13 * ``` */ function fromErrorEvent(event) { if (event.error instanceof Error) { return event.error; } let err; let shouldPatchStack = false; if (event.error && typeof event.error === "object" && event.error["name"] && event.error["message"]) { // Error-like err = fromObject(event.error); shouldPatchStack = !err.stack; } else if (event.message) { err = new Error(event.message); shouldPatchStack = true; } else { return null; } if (shouldPatchStack) { let stack = ""; if (typeof navigator === "object" && navigator.userAgent.includes("Firefox")) { if (event.filename) { stack = "@" + event.filename; } else { stack = "@debugger eval code"; } } else if (typeof navigator === "object" && navigator.userAgent.includes("Safari") && !navigator.userAgent.includes("Chrome") // Chrome likes to pretend it's Safari ) { if (event.filename) { stack = "@" + event.filename; } else { stack = "global code@"; } } else { stack = `${err.name}: ${event.message}\n at ${event.filename || "<anonymous>"}`; } if (event.lineno && stack !== "global code@") { stack += ":" + event.lineno; } if (event.colno && stack !== "global code@") { stack += ":" + event.colno; } Object.defineProperty(err, "stack", { configurable: true, enumerable: false, writable: true, value: stack, }); } return err; } /** @inner */ function isDOMException(value) { return ((typeof DOMException === "function") && (value instanceof DOMException)) || (value instanceof Error && value.constructor.name === "DOMException"); // Node.js v16- } /** @inner */ function isAggregateError(value) { // @ts-ignore return (typeof AggregateError === "function" && value instanceof AggregateError) || (value instanceof Error && value.constructor.name === "AggregateError"); } /** @inner */ function throwUnsupportedRuntimeError() { throw new NotSupportedError("Unsupported runtime"); } /** * Checks if the error is caused by the given `cause`. This function traverses * the cause chain until it reaches the end. * * The given `cause` can be a value or an error constructor. If it's a value, * the error and the cause will be compared using `===`. If it's an error * constructor, the error and the cause will be compared using `instanceof`. * * @example * ```ts * import { isCausedBy } from "@ayonli/jsext/error"; * * const err1 = "The first error"; * const err2 = new Error("The second error", { cause: err1 }); * * class ThirdError extends Error {} * const err3 = new ThirdError("The third error", { cause: err2 }); * * const err4 = new Error("The fourth error", { cause: err3 }); * * console.log(isCausedBy(err4, ThirdError)); // true * console.log(isCausedBy(err4, err2)); // true * console.log(isCausedBy(err4, err1)); // true * ``` */ function isCausedBy(error, cause) { while (true) { if (error.cause === cause || Object.is(error.cause, cause) || (typeof cause === "function" && error.cause instanceof cause)) { return true; } else if (error.cause instanceof Error) { error = error.cause; } else { break; } } return false; } exports.Exception = error_Exception.default; exports.AlreadyExistsError = error_common.AlreadyExistsError; exports.NetworkError = error_common.NetworkError; exports.NotAllowedError = error_common.NotAllowedError; exports.NotFoundError = error_common.NotFoundError; exports.NotImplementedError = error_common.NotImplementedError; exports.NotSupportedError = error_common.NotSupportedError; exports.TimeoutError = error_common.TimeoutError; exports.fromErrorEvent = fromErrorEvent; exports.fromObject = fromObject; exports.getErrorConstructor = getErrorConstructor; exports.isAggregateError = isAggregateError; exports.isCausedBy = isCausedBy; exports.isDOMException = isDOMException; exports.registerErrorType = registerErrorType; exports.throwUnsupportedRuntimeError = throwUnsupportedRuntimeError; exports.toErrorEvent = toErrorEvent; exports.toObject = toObject; //# sourceMappingURL=error.js.map