@block65/custom-error
Advanced tools
| export interface ErrorInfo { | ||
| reason: string; | ||
| metadata: Record<string, string | number>; | ||
| } | ||
| export interface RetryInfo { | ||
| delay: number; | ||
| } | ||
| export interface BadRequest { | ||
| violations: { | ||
| field: string; | ||
| description: string; | ||
| }[]; | ||
| } | ||
| export interface LocalisedMessage { | ||
| locale: "en"; | ||
| message: string; | ||
| } | ||
| export interface Help { | ||
| url: string; | ||
| description: string; | ||
| } | ||
| export interface QuotaFailure { | ||
| violations: { | ||
| /** | ||
| * subject of which quota check failed ie: `account:1234567` | ||
| */ | ||
| subject: string; | ||
| /** | ||
| * description of quota failure | ||
| */ | ||
| description: string; | ||
| }[]; | ||
| } | ||
| export type ErrorDetail = ErrorInfo | RetryInfo | QuotaFailure | BadRequest | LocalisedMessage | Help; |
| export {}; |
| export declare function withNullProto<const T extends Record<PropertyKey, unknown>>(obj: T): T; |
| export function withNullProto(obj) { | ||
| return Object.assign(Object.create(null), obj); | ||
| } |
+121
-74
@@ -1,3 +0,4 @@ | ||
| type DebugData = Record<string, unknown>; | ||
| export declare enum Status { | ||
| import type { ErrorDetail } from "./types.js"; | ||
| export type DebugData = Record<string, unknown>; | ||
| declare enum StatusCode { | ||
| OK = 0, | ||
@@ -21,43 +22,50 @@ CANCELLED = 1, | ||
| } | ||
| export interface ErrorInfo { | ||
| reason: string; | ||
| metadata: Record<string, string>; | ||
| } | ||
| export interface RetryInfo { | ||
| delay: number; | ||
| } | ||
| export interface BadRequest { | ||
| violations: { | ||
| field: string; | ||
| description: string; | ||
| }[]; | ||
| } | ||
| export interface LocalisedMessage { | ||
| locale: 'en'; | ||
| message: string; | ||
| } | ||
| export interface Help { | ||
| url: string; | ||
| description: string; | ||
| } | ||
| export interface QuotaFailure { | ||
| violations: { | ||
| /** | ||
| * subject of which quota check failed ie: `account:1234567` | ||
| */ | ||
| subject: string; | ||
| /** | ||
| * description of quota failure | ||
| */ | ||
| description: string; | ||
| }[]; | ||
| } | ||
| export type ErrorDetail = ErrorInfo | RetryInfo | QuotaFailure | BadRequest | LocalisedMessage | Help; | ||
| export interface CustomErrorSerialized { | ||
| code: Status; | ||
| status: keyof typeof Status; | ||
| message?: string; | ||
| details?: ErrorDetail[]; | ||
| } | ||
| export type { StatusCode }; | ||
| type SerializedError<T extends StatusCode | number> = { | ||
| readonly debug?: DebugData; | ||
| readonly stack?: string; | ||
| readonly cause?: SerializedError<StatusCode>[]; | ||
| readonly details?: ErrorDetail[]; | ||
| readonly name: string; | ||
| readonly message: string; | ||
| readonly code: T; | ||
| }; | ||
| export declare class CustomError extends Error { | ||
| #private; | ||
| static readonly OK = StatusCode.OK; | ||
| static readonly CANCELLED = StatusCode.CANCELLED; | ||
| static readonly UNKNOWN = StatusCode.UNKNOWN; | ||
| static readonly INVALID_ARGUMENT = StatusCode.INVALID_ARGUMENT; | ||
| static readonly DEADLINE_EXCEEDED = StatusCode.DEADLINE_EXCEEDED; | ||
| static readonly NOT_FOUND = StatusCode.NOT_FOUND; | ||
| static readonly ALREADY_EXISTS = StatusCode.ALREADY_EXISTS; | ||
| static readonly PERMISSION_DENIED = StatusCode.PERMISSION_DENIED; | ||
| static readonly RESOURCE_EXHAUSTED = StatusCode.RESOURCE_EXHAUSTED; | ||
| static readonly FAILED_PRECONDITION = StatusCode.FAILED_PRECONDITION; | ||
| static readonly ABORTED = StatusCode.ABORTED; | ||
| static readonly OUT_OF_RANGE = StatusCode.OUT_OF_RANGE; | ||
| static readonly UNIMPLEMENTED = StatusCode.UNIMPLEMENTED; | ||
| static readonly INTERNAL = StatusCode.INTERNAL; | ||
| static readonly UNAVAILABLE = StatusCode.UNAVAILABLE; | ||
| static readonly DATA_LOSS = StatusCode.DATA_LOSS; | ||
| static readonly UNAUTHENTICATED = StatusCode.UNAUTHENTICATED; | ||
| static http: Readonly<{ | ||
| readonly 0: 200; | ||
| readonly 1: 299; | ||
| readonly 2: 500; | ||
| readonly 3: 400; | ||
| readonly 4: 504; | ||
| readonly 5: 404; | ||
| readonly 6: 409; | ||
| readonly 7: 403; | ||
| readonly 8: 403; | ||
| readonly 9: 400; | ||
| readonly 10: 299; | ||
| readonly 11: 400; | ||
| readonly 12: 501; | ||
| readonly 13: 500; | ||
| readonly 14: 503; | ||
| readonly 15: 500; | ||
| readonly 16: 401; | ||
| }>; | ||
| /** | ||
@@ -76,12 +84,5 @@ * The previous error that occurred, useful if "wrapping" an error to hide | ||
| * Status code suitable to coarsely determine the reason for error | ||
| * @type {Status} | ||
| */ | ||
| code: Status; | ||
| readonly code: StatusCode; | ||
| /** | ||
| * Contains arbitrary debug data for developer troubleshooting | ||
| * @type {DebugData} | ||
| * @private | ||
| */ | ||
| private debugData?; | ||
| /** | ||
| * | ||
@@ -91,16 +92,9 @@ * @param {string} message Developer facing message, in English. | ||
| */ | ||
| constructor(message: string, cause?: Error | CustomError | unknown); | ||
| constructor(message?: string, cause?: Error | CustomError | unknown); | ||
| static isCustomError(value: unknown): value is CustomError; | ||
| /** | ||
| * Add arbitrary debug data to the error object for developer troubleshooting | ||
| * @return {DebugData | undefined} | ||
| */ | ||
| debug(): DebugData | undefined; | ||
| debug(data: DebugData | undefined): this; | ||
| debug(data: DebugData): this; | ||
| /** | ||
| * Human readable representation of the error code | ||
| * @return {keyof typeof Status} | ||
| */ | ||
| get status(): keyof typeof Status; | ||
| /** | ||
| * Adds further error details suitable for end user consumption | ||
@@ -113,27 +107,80 @@ * @param {ErrorDetail} details | ||
| * A "safe" serialised version of the error designed for end user consumption | ||
| * @return {CustomErrorSerialized} | ||
| */ | ||
| serialize(): CustomErrorSerialized; | ||
| toJSONSummary(): { | ||
| details?: ErrorDetail[]; | ||
| code: StatusCode; | ||
| message?: string; | ||
| }; | ||
| /** | ||
| * JSON representation of the error object. | ||
| * | ||
| * Use {serialize} instead if you need to send this error over the wire | ||
| * | ||
| * @return {object} | ||
| * JSON representation of the error object that can be "hydrated" later | ||
| */ | ||
| toJSON(): Omit<CustomError, 'addDetail' | 'serialize' | 'debug' | 'toJSON'> & { | ||
| debug?: DebugData; | ||
| toJSON(): { | ||
| readonly debug?: DebugData; | ||
| readonly stack?: string; | ||
| readonly cause?: any; | ||
| readonly details?: ErrorDetail[]; | ||
| readonly name: string; | ||
| readonly message: string; | ||
| readonly code: StatusCode; | ||
| }; | ||
| /** | ||
| * "Hydrates" a previously serialised error object | ||
| * @param {CustomErrorSerialized} params | ||
| * @return {CustomError} | ||
| */ | ||
| static fromJSON(params: CustomErrorSerialized): CustomError; | ||
| static fromJSON<const T extends StatusCode | number>(params: SerializedError<T>): { | ||
| readonly code: T; | ||
| /** | ||
| * The previous error that occurred, useful if "wrapping" an error to hide | ||
| * sensitive details | ||
| * @type {Error | CustomError | unknown} | ||
| */ | ||
| readonly cause?: Error | CustomError | unknown; | ||
| /** | ||
| * Further error details suitable for end user consumption | ||
| * @type {ErrorDetail[]} | ||
| */ | ||
| details?: ErrorDetail[]; | ||
| /** | ||
| * Contains arbitrary debug data for developer troubleshooting | ||
| * @type {DebugData} | ||
| * @private | ||
| */ | ||
| "__#1@#debug"?: DebugData; | ||
| /** | ||
| * Add arbitrary debug data to the error object for developer troubleshooting | ||
| */ | ||
| debug(data: DebugData): /*elided*/ any; | ||
| /** | ||
| * Adds further error details suitable for end user consumption | ||
| * @param {ErrorDetail} details | ||
| * @return {this} | ||
| */ | ||
| addDetail(...details: ErrorDetail[]): /*elided*/ any; | ||
| /** | ||
| * A "safe" serialised version of the error designed for end user consumption | ||
| */ | ||
| toJSONSummary(): { | ||
| details?: ErrorDetail[]; | ||
| code: StatusCode; | ||
| message?: string; | ||
| }; | ||
| /** | ||
| * JSON representation of the error object that can be "hydrated" later | ||
| */ | ||
| toJSON(): { | ||
| readonly debug?: DebugData; | ||
| readonly stack?: string; | ||
| readonly cause?: any; | ||
| readonly details?: ErrorDetail[]; | ||
| readonly name: string; | ||
| readonly message: string; | ||
| readonly code: StatusCode; | ||
| }; | ||
| name: string; | ||
| message: string; | ||
| stack?: string; | ||
| }; | ||
| /** | ||
| * An automatically determined HTTP status code | ||
| * @return {number} | ||
| */ | ||
| static suggestHttpResponseCode(err: Error | CustomError | unknown): number; | ||
| static suggestHttpResponseCode(err: Error | CustomError | unknown): 200 | 299 | 500 | 400 | 504 | 404 | 409 | 403 | 501 | 503 | 401; | ||
| } | ||
| export {}; |
+89
-90
@@ -1,46 +0,61 @@ | ||
| export var Status; | ||
| (function (Status) { | ||
| Status[Status["OK"] = 0] = "OK"; | ||
| Status[Status["CANCELLED"] = 1] = "CANCELLED"; | ||
| Status[Status["UNKNOWN"] = 2] = "UNKNOWN"; | ||
| Status[Status["INVALID_ARGUMENT"] = 3] = "INVALID_ARGUMENT"; | ||
| Status[Status["DEADLINE_EXCEEDED"] = 4] = "DEADLINE_EXCEEDED"; | ||
| Status[Status["NOT_FOUND"] = 5] = "NOT_FOUND"; | ||
| Status[Status["ALREADY_EXISTS"] = 6] = "ALREADY_EXISTS"; | ||
| Status[Status["PERMISSION_DENIED"] = 7] = "PERMISSION_DENIED"; | ||
| Status[Status["RESOURCE_EXHAUSTED"] = 8] = "RESOURCE_EXHAUSTED"; | ||
| Status[Status["FAILED_PRECONDITION"] = 9] = "FAILED_PRECONDITION"; | ||
| Status[Status["ABORTED"] = 10] = "ABORTED"; | ||
| Status[Status["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE"; | ||
| Status[Status["UNIMPLEMENTED"] = 12] = "UNIMPLEMENTED"; | ||
| Status[Status["INTERNAL"] = 13] = "INTERNAL"; | ||
| Status[Status["UNAVAILABLE"] = 14] = "UNAVAILABLE"; | ||
| Status[Status["DATA_LOSS"] = 15] = "DATA_LOSS"; | ||
| Status[Status["UNAUTHENTICATED"] = 16] = "UNAUTHENTICATED"; | ||
| })(Status || (Status = {})); | ||
| const CUSTOM_ERROR_SYM = Symbol.for('CustomError'); | ||
| const defaultHttpMapping = new Map([ | ||
| [Status.OK, 200], | ||
| [Status.INVALID_ARGUMENT, 400], | ||
| [Status.FAILED_PRECONDITION, 400], | ||
| [Status.OUT_OF_RANGE, 400], | ||
| [Status.UNAUTHENTICATED, 401], | ||
| [Status.PERMISSION_DENIED, 403], | ||
| [Status.NOT_FOUND, 404], | ||
| [Status.ABORTED, 409], | ||
| [Status.ALREADY_EXISTS, 409], | ||
| [Status.RESOURCE_EXHAUSTED, 403], | ||
| [Status.CANCELLED, 499], | ||
| [Status.DATA_LOSS, 500], | ||
| [Status.UNKNOWN, 500], | ||
| [Status.INTERNAL, 500], | ||
| [Status.UNIMPLEMENTED, 501], | ||
| // [Code.LOCAL_OUTAGE, 502], | ||
| [Status.UNAVAILABLE, 503], | ||
| [Status.DEADLINE_EXCEEDED, 504], | ||
| ]); | ||
| function withNullProto(obj) { | ||
| return Object.assign(Object.create(null), obj); | ||
| } | ||
| import { isErrorLike } from "serialize-error"; | ||
| import { withNullProto } from "./utils.js"; | ||
| var StatusCode; | ||
| (function (StatusCode) { | ||
| StatusCode[StatusCode["OK"] = 0] = "OK"; | ||
| StatusCode[StatusCode["CANCELLED"] = 1] = "CANCELLED"; | ||
| StatusCode[StatusCode["UNKNOWN"] = 2] = "UNKNOWN"; | ||
| StatusCode[StatusCode["INVALID_ARGUMENT"] = 3] = "INVALID_ARGUMENT"; | ||
| StatusCode[StatusCode["DEADLINE_EXCEEDED"] = 4] = "DEADLINE_EXCEEDED"; | ||
| StatusCode[StatusCode["NOT_FOUND"] = 5] = "NOT_FOUND"; | ||
| StatusCode[StatusCode["ALREADY_EXISTS"] = 6] = "ALREADY_EXISTS"; | ||
| StatusCode[StatusCode["PERMISSION_DENIED"] = 7] = "PERMISSION_DENIED"; | ||
| StatusCode[StatusCode["RESOURCE_EXHAUSTED"] = 8] = "RESOURCE_EXHAUSTED"; | ||
| StatusCode[StatusCode["FAILED_PRECONDITION"] = 9] = "FAILED_PRECONDITION"; | ||
| StatusCode[StatusCode["ABORTED"] = 10] = "ABORTED"; | ||
| StatusCode[StatusCode["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE"; | ||
| StatusCode[StatusCode["UNIMPLEMENTED"] = 12] = "UNIMPLEMENTED"; | ||
| StatusCode[StatusCode["INTERNAL"] = 13] = "INTERNAL"; | ||
| StatusCode[StatusCode["UNAVAILABLE"] = 14] = "UNAVAILABLE"; | ||
| StatusCode[StatusCode["DATA_LOSS"] = 15] = "DATA_LOSS"; | ||
| StatusCode[StatusCode["UNAUTHENTICATED"] = 16] = "UNAUTHENTICATED"; | ||
| })(StatusCode || (StatusCode = {})); | ||
| const kCustomError = Symbol.for("CustomError"); | ||
| export class CustomError extends Error { | ||
| static OK = StatusCode.OK; | ||
| static CANCELLED = StatusCode.CANCELLED; | ||
| static UNKNOWN = StatusCode.UNKNOWN; | ||
| static INVALID_ARGUMENT = StatusCode.INVALID_ARGUMENT; | ||
| static DEADLINE_EXCEEDED = StatusCode.DEADLINE_EXCEEDED; | ||
| static NOT_FOUND = StatusCode.NOT_FOUND; | ||
| static ALREADY_EXISTS = StatusCode.ALREADY_EXISTS; | ||
| static PERMISSION_DENIED = StatusCode.PERMISSION_DENIED; | ||
| static RESOURCE_EXHAUSTED = StatusCode.RESOURCE_EXHAUSTED; | ||
| static FAILED_PRECONDITION = StatusCode.FAILED_PRECONDITION; | ||
| static ABORTED = StatusCode.ABORTED; | ||
| static OUT_OF_RANGE = StatusCode.OUT_OF_RANGE; | ||
| static UNIMPLEMENTED = StatusCode.UNIMPLEMENTED; | ||
| static INTERNAL = StatusCode.INTERNAL; | ||
| static UNAVAILABLE = StatusCode.UNAVAILABLE; | ||
| static DATA_LOSS = StatusCode.DATA_LOSS; | ||
| static UNAUTHENTICATED = StatusCode.UNAUTHENTICATED; | ||
| static http = Object.freeze({ | ||
| [CustomError.OK]: 200, | ||
| [CustomError.CANCELLED]: 299, | ||
| [CustomError.UNKNOWN]: 500, | ||
| [CustomError.INVALID_ARGUMENT]: 400, | ||
| [CustomError.DEADLINE_EXCEEDED]: 504, | ||
| [CustomError.NOT_FOUND]: 404, | ||
| [CustomError.ALREADY_EXISTS]: 409, | ||
| [CustomError.PERMISSION_DENIED]: 403, | ||
| [CustomError.RESOURCE_EXHAUSTED]: 403, | ||
| [CustomError.FAILED_PRECONDITION]: 400, | ||
| [CustomError.ABORTED]: 299, | ||
| [CustomError.OUT_OF_RANGE]: 400, | ||
| [CustomError.UNIMPLEMENTED]: 501, | ||
| [CustomError.INTERNAL]: 500, | ||
| [CustomError.UNAVAILABLE]: 503, | ||
| [CustomError.DATA_LOSS]: 500, | ||
| [CustomError.UNAUTHENTICATED]: 401, | ||
| }); | ||
| /** | ||
@@ -59,5 +74,4 @@ * The previous error that occurred, useful if "wrapping" an error to hide | ||
| * Status code suitable to coarsely determine the reason for error | ||
| * @type {Status} | ||
| */ | ||
| code = Status.UNKNOWN; | ||
| code = CustomError.UNKNOWN; | ||
| /** | ||
@@ -68,3 +82,3 @@ * Contains arbitrary debug data for developer troubleshooting | ||
| */ | ||
| debugData; | ||
| #debug; | ||
| /** | ||
@@ -85,20 +99,13 @@ * | ||
| static isCustomError(value) { | ||
| return !!value && typeof value === 'object' && CUSTOM_ERROR_SYM in value; | ||
| return !!value && typeof value === "object" && kCustomError in value; | ||
| } | ||
| debug(data) { | ||
| if (arguments.length > 0) { | ||
| this.debugData = withNullProto({ | ||
| ...this.debugData, | ||
| ...data, | ||
| }); | ||
| return this; | ||
| } | ||
| return this.debugData; | ||
| } | ||
| /** | ||
| * Human readable representation of the error code | ||
| * @return {keyof typeof Status} | ||
| * Add arbitrary debug data to the error object for developer troubleshooting | ||
| */ | ||
| get status() { | ||
| return Status[this.code]; | ||
| debug(data) { | ||
| this.#debug = withNullProto({ | ||
| ...this.#debug, | ||
| ...data, | ||
| }); | ||
| return this; | ||
| } | ||
@@ -116,8 +123,6 @@ /** | ||
| * A "safe" serialised version of the error designed for end user consumption | ||
| * @return {CustomErrorSerialized} | ||
| */ | ||
| serialize() { | ||
| const localised = this.details?.find((detail) => 'locale' in detail); | ||
| return withNullProto({ | ||
| message: this.message, | ||
| toJSONSummary() { | ||
| const localised = this.details?.find((detail) => "locale" in detail); | ||
| return { | ||
| ...(localised?.message && { | ||
@@ -127,15 +132,9 @@ message: localised.message, | ||
| code: this.code, | ||
| status: this.status, | ||
| ...(this.details && { details: this.details }), | ||
| }); | ||
| }; | ||
| } | ||
| /** | ||
| * JSON representation of the error object. | ||
| * | ||
| * Use {serialize} instead if you need to send this error over the wire | ||
| * | ||
| * @return {object} | ||
| * JSON representation of the error object that can be "hydrated" later | ||
| */ | ||
| toJSON() { | ||
| const debug = this.debug(); | ||
| return withNullProto({ | ||
@@ -145,14 +144,13 @@ name: this.name, | ||
| code: this.code, | ||
| status: this.status, | ||
| ...(this.details && { details: this.details }), | ||
| ...(this.cause instanceof Error && { | ||
| cause: 'toJSON' in this.cause && typeof this.cause.toJSON === 'function' | ||
| ...(isErrorLike(this.cause) && { | ||
| cause: "toJSON" in this.cause && typeof this.cause.toJSON === "function" | ||
| ? this.cause.toJSON() | ||
| : { | ||
| message: this.cause.message, | ||
| name: 'Error', | ||
| name: "Error", | ||
| }, | ||
| }), | ||
| ...(this.stack && { stack: this.stack }), | ||
| ...(debug && { debug }), | ||
| ...(this.#debug && { debug: this.#debug }), | ||
| }); | ||
@@ -162,9 +160,11 @@ } | ||
| * "Hydrates" a previously serialised error object | ||
| * @param {CustomErrorSerialized} params | ||
| * @return {CustomError} | ||
| */ | ||
| static fromJSON(params) { | ||
| const { code = Status.UNKNOWN, message, details = [] } = params; | ||
| const err = new CustomError(message || (Status[params.code] || params.code || 'Error').toString()).debug({ params }); | ||
| err.code = code; | ||
| const { message, details, code } = params; | ||
| class ImportedError extends CustomError { | ||
| code = code; | ||
| } | ||
| const err = new ImportedError(message).debug({ | ||
| params, | ||
| }); | ||
| if (details) { | ||
@@ -177,11 +177,10 @@ err.addDetail(...details); | ||
| * An automatically determined HTTP status code | ||
| * @return {number} | ||
| */ | ||
| static suggestHttpResponseCode(err) { | ||
| const code = CustomError.isCustomError(err) ? err.code : Status.UNKNOWN; | ||
| return defaultHttpMapping.get(code) || 500; | ||
| return ((CustomError.isCustomError(err) && CustomError.http[err.code]) || | ||
| CustomError.http[CustomError.UNKNOWN]); | ||
| } | ||
| } | ||
| // Mark all instances of 'CustomError' | ||
| Object.defineProperty(CustomError.prototype, CUSTOM_ERROR_SYM, { | ||
| Object.defineProperty(CustomError.prototype, kCustomError, { | ||
| value: true, | ||
@@ -192,4 +191,4 @@ enumerable: false, | ||
| // allow enumeration of status getter | ||
| Object.defineProperty(CustomError.prototype, 'status', { | ||
| Object.defineProperty(CustomError.prototype, "status", { | ||
| enumerable: true, | ||
| }); |
@@ -1,2 +0,3 @@ | ||
| export * from './custom-error.js'; | ||
| export * from './serialize.js'; | ||
| export * from "./custom-error.js"; | ||
| export * from "./serialize.js"; | ||
| export type * from "./types.js"; |
@@ -1,2 +0,5 @@ | ||
| export * from './custom-error.js'; | ||
| export * from './serialize.js'; | ||
| import { addKnownErrorConstructor } from "serialize-error"; | ||
| import { CustomError } from "./custom-error.js"; | ||
| addKnownErrorConstructor(CustomError); | ||
| export * from "./custom-error.js"; | ||
| export * from "./serialize.js"; |
@@ -1,10 +0,4 @@ | ||
| import { CustomError } from './custom-error.js'; | ||
| export interface SerializedError { | ||
| name: string; | ||
| message: string; | ||
| statusCode?: number; | ||
| stack?: string; | ||
| cause?: SerializedError[] | unknown; | ||
| [key: string]: unknown; | ||
| } | ||
| export declare function serializeError(err: unknown | Error | CustomError): SerializedError; | ||
| import { type ErrorLike, type ErrorObject } from "serialize-error"; | ||
| import { CustomError } from "./custom-error.js"; | ||
| declare function recursiveSerializeError(err: unknown | ErrorLike | Error | CustomError): ErrorObject; | ||
| export { recursiveSerializeError as serializeError }; |
+24
-27
@@ -1,7 +0,7 @@ | ||
| import { serializeError as serialize } from 'serialize-error'; | ||
| import { CustomError } from './custom-error.js'; | ||
| function flattenPreviousErrors(err, accum = []) { | ||
| if (err instanceof Error) { | ||
| if ('cause' in err && err.cause) { | ||
| return flattenPreviousErrors(err.cause, [...accum, err]); | ||
| import { isErrorLike, serializeError, } from "serialize-error"; | ||
| import { CustomError } from "./custom-error.js"; | ||
| function flatten(err, accum = []) { | ||
| if (isErrorLike(err)) { | ||
| if ("cause" in err && err.cause) { | ||
| return flatten(err.cause, [...accum, err]); | ||
| } | ||
@@ -12,25 +12,22 @@ return [...accum, err]; | ||
| } | ||
| export function serializeError(err) { | ||
| function recursiveSerializeError(err) { | ||
| if (CustomError.isCustomError(err)) { | ||
| const previousErrors = 'cause' in err && err.cause | ||
| ? flattenPreviousErrors(err.cause) | ||
| : undefined; | ||
| const { cause, ...rest } = serializeError(err); | ||
| const causes = cause ? flatten(cause) : undefined; | ||
| return { | ||
| ...serialize(err), | ||
| message: err.message, | ||
| name: err.name, | ||
| status: err.status, | ||
| ...(previousErrors && { cause: previousErrors.map(serializeError) }), | ||
| ...('debug' in err && { debug: err.debug() }), | ||
| ...rest, | ||
| ...(causes && { | ||
| cause: causes.map(recursiveSerializeError), | ||
| }), | ||
| }; | ||
| } | ||
| if (err instanceof Error) { | ||
| const { name, message, stack, cause, code, ...debug } = serialize(err); | ||
| if (isErrorLike(err)) { | ||
| const { name, message, stack, cause, code, ...rest } = serializeError(err); | ||
| return { | ||
| message: message || 'Error', | ||
| name: name || 'Error', | ||
| code, | ||
| stack, | ||
| ...(!!cause && { cause: [serializeError(cause)] }), | ||
| ...(debug && { debug }), | ||
| ...(name && { name }), | ||
| ...(message && { message }), | ||
| ...(code && { code }), | ||
| ...(stack && { stack }), | ||
| ...(!!cause && { cause: [recursiveSerializeError(cause)] }), | ||
| ...(rest && { debug: rest }), | ||
| }; | ||
@@ -40,10 +37,10 @@ } | ||
| return { | ||
| name: 'Error', | ||
| name: "Error", | ||
| message: String(err), | ||
| stack: Error().stack, | ||
| debug: { | ||
| typeofErr: typeof err, | ||
| err, | ||
| err: String(err), | ||
| }, | ||
| }; | ||
| } | ||
| export { recursiveSerializeError as serializeError }; |
+5
-6
@@ -1,8 +0,7 @@ | ||
| Copyright 2019 Block65 Pte Ltd | ||
| Copyright 2025 Block65 Pte. Ltd. | ||
| This software and associated documentation files (the "Software") is proprietary | ||
| and unlicensed. | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
| You are not permitted to reproduce/copy/distribute any part of the Software | ||
| unless as part of similarly proprietary and unlicensed Block65 Pte Ltd | ||
| intellectual property. | ||
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
+10
-21
| { | ||
| "name": "@block65/custom-error", | ||
| "version": "12.2.0", | ||
| "version": "13.0.0-rc.0", | ||
| "private": false, | ||
| "license": "UNLICENSED", | ||
| "license": "MIT", | ||
| "type": "module", | ||
@@ -18,25 +18,14 @@ "exports": { | ||
| "dependencies": { | ||
| "serialize-error": "^11.0.3" | ||
| "serialize-error": "^12.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@babel/core": "^7.24.4", | ||
| "@babel/preset-env": "^7.24.4", | ||
| "@babel/preset-typescript": "^7.24.1", | ||
| "@block65/eslint-config": "11", | ||
| "@types/jest": "^29.5.12", | ||
| "@types/node": "^20.12.7", | ||
| "@typescript-eslint/eslint-plugin": "^7.7.1", | ||
| "@typescript-eslint/parser": "^7.7.1", | ||
| "eslint": "8", | ||
| "eslint-plugin-import": "^2.29.1", | ||
| "eslint-plugin-prettier": "^5.1.3", | ||
| "jest": "^29.7.0", | ||
| "@jest/globals": "^29.7.0", | ||
| "prettier": "^3.2.5", | ||
| "rimraf": "^5.0.5", | ||
| "typescript": "^5.4.5" | ||
| "@tsconfig/node18": "^18.2.4", | ||
| "@tsconfig/strictest": "^2.0.5", | ||
| "vitest": "^2.1.8", | ||
| "@biomejs/biome": "^1.9.4", | ||
| "@types/node": "^20.17.12", | ||
| "typescript": "^5.7.3" | ||
| }, | ||
| "packageManager": "pnpm@9.0.6+sha256.0624e30eff866cdeb363b15061bdb7fd9425b17bc1bb42c22f5f4efdea21f6b3", | ||
| "engines": { | ||
| "node": ">=18.0.0" | ||
| "node": ">=20.0.0" | ||
| }, | ||
@@ -43,0 +32,0 @@ "scripts": { |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Explicitly Unlicensed Item
LicenseSomething was found which is explicitly marked as unlicensed.
Found 1 instance in 1 package
Misc. License Issues
LicenseA package's licensing information has fine-grained problems.
Found 1 instance in 1 package
Unidentified License
LicenseSomething that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
17538
31.81%6
-62.5%12
50%0
-100%0
-100%0
-100%100
Infinity%462
20.94%2
100%1
Infinity%+ Added
+ Added
- Removed
- Removed
Updated