@block65/custom-error
Advanced tools
| import { isErrorLike } from "serialize-error"; | ||
| import type { ErrorDetail, LocalisedMessage } from "./types.ts"; | ||
| import { withNullProto } from "./utils.ts"; | ||
| export type DebugData = Record<string, unknown>; | ||
| const StatusCode = { | ||
| OK: 0 as const, | ||
| CANCELLED: 1 as const, | ||
| UNKNOWN: 2 as const, | ||
| INVALID_ARGUMENT: 3 as const, | ||
| DEADLINE_EXCEEDED: 4 as const, | ||
| NOT_FOUND: 5 as const, | ||
| ALREADY_EXISTS: 6 as const, | ||
| PERMISSION_DENIED: 7 as const, | ||
| RESOURCE_EXHAUSTED: 8 as const, | ||
| FAILED_PRECONDITION: 9 as const, | ||
| ABORTED: 10 as const, | ||
| OUT_OF_RANGE: 11 as const, | ||
| UNIMPLEMENTED: 12 as const, | ||
| INTERNAL: 13 as const, | ||
| UNAVAILABLE: 14 as const, | ||
| DATA_LOSS: 15 as const, | ||
| UNAUTHENTICATED: 16 as const, | ||
| } | ||
| // just export the type, we CustomError.XX should be used for the actual code | ||
| export type StatusCode = typeof StatusCode[keyof typeof StatusCode]; | ||
| const statusCodes: ReadonlySet<number> = new Set(Object.values(StatusCode)); | ||
| export function isStatusCode(value: unknown): value is StatusCode { | ||
| return typeof value === 'number' && statusCodes.has(value); | ||
| } | ||
| export type SerializedError<T extends StatusCode> = { | ||
| readonly debug?: DebugData; | ||
| readonly stack?: string; | ||
| readonly cause?: SerializedError<StatusCode>[]; | ||
| readonly details?: ErrorDetail[]; | ||
| readonly name: string; | ||
| readonly message: string; | ||
| readonly code: T; | ||
| }; | ||
| const kCustomError = Symbol.for("CustomError"); | ||
| export class CustomError extends Error { | ||
| 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 = Object.freeze({ | ||
| [CustomError.OK]: 200, | ||
| [CustomError.CANCELLED]: 499, | ||
| [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]: 429, | ||
| [CustomError.FAILED_PRECONDITION]: 400, | ||
| [CustomError.ABORTED]: 409, | ||
| [CustomError.OUT_OF_RANGE]: 422, | ||
| [CustomError.UNIMPLEMENTED]: 501, | ||
| [CustomError.INTERNAL]: 500, | ||
| [CustomError.UNAVAILABLE]: 503, | ||
| [CustomError.DATA_LOSS]: 500, | ||
| [CustomError.UNAUTHENTICATED]: 401, | ||
| } as const); | ||
| /** | ||
| * The previous error that occurred, useful if "wrapping" an error to hide | ||
| * sensitive details | ||
| * @type {Error | CustomError | unknown} | ||
| */ | ||
| public override readonly cause?: Error | CustomError | unknown; | ||
| /** | ||
| * Further error details suitable for end user consumption | ||
| * @type {ErrorDetail[]} | ||
| */ | ||
| public details?: ErrorDetail[]; | ||
| /** | ||
| * Status code suitable to coarsely determine the reason for error | ||
| */ | ||
| readonly code: StatusCode = CustomError.UNKNOWN; | ||
| /** | ||
| * Contains arbitrary debug data for developer troubleshooting | ||
| * @type {DebugData} | ||
| * @private | ||
| */ | ||
| #debug?: DebugData; | ||
| /** | ||
| * | ||
| * @param {string} message Developer facing message, in English. | ||
| * @param {Error | CustomError | unknown} cause | ||
| */ | ||
| constructor(message?: string, cause?: Error | CustomError | unknown) { | ||
| super(message, { cause }); | ||
| this.cause = cause; | ||
| // FF doesnt have captureStackTrace | ||
| if ('captureStackTrace' in Error) { | ||
| Error.captureStackTrace(this, this.constructor); | ||
| } | ||
| Object.setPrototypeOf(this, new.target.prototype); | ||
| } | ||
| public static isCustomError(value: unknown): value is CustomError { | ||
| return !!value && typeof value === "object" && kCustomError in value; | ||
| } | ||
| /** | ||
| * Add arbitrary debug data to the error object for developer troubleshooting | ||
| */ | ||
| public debug(data: DebugData): this { | ||
| this.#debug = withNullProto({ | ||
| ...this.#debug, | ||
| ...data, | ||
| }); | ||
| return this; | ||
| } | ||
| /** | ||
| * Adds further error details suitable for end user consumption | ||
| * @param {ErrorDetail} details | ||
| * @return {this} | ||
| */ | ||
| public addDetail(...details: ErrorDetail[]) { | ||
| this.details = (this.details || []).concat(details); | ||
| return this; | ||
| } | ||
| /** | ||
| * A "safe" serialised version of the error designed for end user consumption | ||
| */ | ||
| public toJSONSummary() { | ||
| const localised = this.details?.find( | ||
| (detail): detail is LocalisedMessage => "locale" in detail, | ||
| ); | ||
| return { | ||
| ...(localised?.message && { | ||
| message: localised.message, | ||
| }), | ||
| code: this.code, | ||
| ...(this.details && { details: this.details }), | ||
| }; | ||
| } | ||
| /** | ||
| * JSON representation of the error object that can be "hydrated" later | ||
| */ | ||
| public toJSON() { | ||
| return withNullProto({ | ||
| name: this.name, | ||
| message: this.message, | ||
| code: this.code, | ||
| ...(this.details && { details: this.details }), | ||
| ...(isErrorLike(this.cause) && { | ||
| cause: | ||
| "toJSON" in this.cause && typeof this.cause.toJSON === "function" | ||
| ? this.cause.toJSON() | ||
| : { | ||
| message: this.cause.message, | ||
| name: "Error", | ||
| }, | ||
| }), | ||
| ...(this.stack && { stack: this.stack }), | ||
| ...(this.#debug && { debug: this.#debug }), | ||
| }); | ||
| } | ||
| /** | ||
| * "Hydrates" a previously serialised error object | ||
| */ | ||
| public static fromJSON<const T extends StatusCode>( | ||
| params: SerializedError<T>, | ||
| ) { | ||
| const { message, details, code } = params; | ||
| class ImportedError extends CustomError { | ||
| override readonly code = code as StatusCode; | ||
| } | ||
| const err = new ImportedError(message).debug({ | ||
| params, | ||
| }); | ||
| if (details) { | ||
| err.addDetail(...details); | ||
| } | ||
| return err; | ||
| } | ||
| /** | ||
| * An automatically determined HTTP status code | ||
| */ | ||
| public static suggestHttpResponseCode(err: Error | CustomError | unknown) { | ||
| return ( | ||
| (CustomError.isCustomError(err) && CustomError.http[err.code]) || | ||
| CustomError.http[CustomError.UNKNOWN] || | ||
| 500 | ||
| ); | ||
| } | ||
| } | ||
| // Mark all instances of 'CustomError' | ||
| Object.defineProperty(CustomError.prototype, kCustomError, { | ||
| value: true, | ||
| enumerable: false, | ||
| writable: false, | ||
| }); | ||
| // allow enumeration of status getter | ||
| Object.defineProperty(CustomError.prototype, "status", { | ||
| enumerable: true, | ||
| }); |
+11
| import { addKnownErrorConstructor } from "serialize-error"; | ||
| import { CustomError } from "./custom-error.ts"; | ||
| try { | ||
| addKnownErrorConstructor(CustomError); | ||
| } catch{} | ||
| export * from "./custom-error.ts"; | ||
| export * from "./serialize.ts"; | ||
| export type * from "./types.ts"; |
| import { | ||
| type ErrorLike, | ||
| type ErrorObject, | ||
| isErrorLike, | ||
| serializeError, | ||
| } from "serialize-error"; | ||
| import { CustomError } from "./custom-error.ts"; | ||
| function flatten( | ||
| err: unknown | ErrorLike | Error | CustomError, | ||
| accum: (Error | CustomError)[] = [], | ||
| ): (Error | CustomError)[] { | ||
| if (isErrorLike(err)) { | ||
| if ("cause" in err && err.cause) { | ||
| return flatten(err.cause, [...accum, err]); | ||
| } | ||
| return [...accum, err]; | ||
| } | ||
| return accum; | ||
| } | ||
| function recursiveSerializeError( | ||
| err: unknown | ErrorLike | Error | CustomError, | ||
| ): ErrorObject { | ||
| if (CustomError.isCustomError(err)) { | ||
| const { cause, ...rest } = serializeError(err); | ||
| const causes = cause ? flatten(cause) : undefined; | ||
| return { | ||
| ...rest, | ||
| ...(causes && { | ||
| cause: causes.map(recursiveSerializeError), | ||
| }), | ||
| } satisfies ErrorObject; | ||
| } | ||
| if (isErrorLike(err)) { | ||
| const { name, message, stack, cause, code, ...rest } = serializeError(err); | ||
| return { | ||
| ...(name && { name }), | ||
| ...(message && { message }), | ||
| ...(code && { code }), | ||
| ...(stack && { stack }), | ||
| ...(!!cause && { cause: [recursiveSerializeError(cause)] }), | ||
| ...(rest && { debug: rest }), | ||
| }; | ||
| } | ||
| // Not an error object, maybe primitive or null, undefined | ||
| return { | ||
| name: "Error", | ||
| message: String(err), | ||
| debug: { | ||
| typeofErr: typeof err, | ||
| err: String(err), | ||
| }, | ||
| }; | ||
| } | ||
| export { recursiveSerializeError as serializeError }; |
+43
| 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 function withNullProto<const T extends Record<PropertyKey, unknown>>( | ||
| obj: T, | ||
| ) { | ||
| return Object.assign<T, T>(Object.create(null), obj); | ||
| } |
+15
-18
| { | ||
| "name": "@block65/custom-error", | ||
| "version": "13.0.0-rc.0", | ||
| "version": "14.0.0", | ||
| "private": false, | ||
| "license": "MIT", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/block65/custom-error" | ||
| }, | ||
| "type": "module", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/lib/index.d.ts", | ||
| "default": "./dist/lib/index.js" | ||
| } | ||
| }, | ||
| "exports": "./lib/main.ts", | ||
| "files": [ | ||
| "dist/lib/*.js", | ||
| "dist/lib/*.d.ts" | ||
| "lib" | ||
| ], | ||
| "dependencies": { | ||
| "serialize-error": "^12.0.0" | ||
| "serialize-error": "^13.0.1" | ||
| }, | ||
| "devDependencies": { | ||
| "@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" | ||
| "@tsconfig/node24": "^24.0.4", | ||
| "@tsconfig/strictest": "^2.0.8", | ||
| "@types/node": "^24.0.0", | ||
| "typescript": "^5.8.0", | ||
| "vitest": "^3.0.0" | ||
| }, | ||
| "engines": { | ||
| "node": ">=20.0.0" | ||
| "node": ">=24.0.0" | ||
| }, | ||
| "scripts": { | ||
| "preversion": "make clean && make test && make" | ||
| "preversion": "make test && make" | ||
| } | ||
| } |
| import type { ErrorDetail } from "./types.js"; | ||
| export type DebugData = Record<string, unknown>; | ||
| declare enum StatusCode { | ||
| OK = 0, | ||
| CANCELLED = 1, | ||
| UNKNOWN = 2, | ||
| INVALID_ARGUMENT = 3, | ||
| DEADLINE_EXCEEDED = 4, | ||
| NOT_FOUND = 5, | ||
| ALREADY_EXISTS = 6, | ||
| PERMISSION_DENIED = 7, | ||
| RESOURCE_EXHAUSTED = 8, | ||
| FAILED_PRECONDITION = 9, | ||
| ABORTED = 10, | ||
| OUT_OF_RANGE = 11, | ||
| UNIMPLEMENTED = 12, | ||
| INTERNAL = 13, | ||
| UNAVAILABLE = 14, | ||
| DATA_LOSS = 15, | ||
| UNAUTHENTICATED = 16 | ||
| } | ||
| 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; | ||
| }>; | ||
| /** | ||
| * 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[]; | ||
| /** | ||
| * Status code suitable to coarsely determine the reason for error | ||
| */ | ||
| readonly code: StatusCode; | ||
| /** | ||
| * | ||
| * @param {string} message Developer facing message, in English. | ||
| * @param {Error | CustomError | unknown} cause | ||
| */ | ||
| 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 | ||
| */ | ||
| debug(data: DebugData): this; | ||
| /** | ||
| * Adds further error details suitable for end user consumption | ||
| * @param {ErrorDetail} details | ||
| * @return {this} | ||
| */ | ||
| addDetail(...details: ErrorDetail[]): this; | ||
| /** | ||
| * 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; | ||
| }; | ||
| /** | ||
| * "Hydrates" a previously serialised error object | ||
| */ | ||
| 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 | ||
| */ | ||
| static suggestHttpResponseCode(err: Error | CustomError | unknown): 200 | 299 | 500 | 400 | 504 | 404 | 409 | 403 | 501 | 503 | 401; | ||
| } |
| 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, | ||
| }); | ||
| /** | ||
| * The previous error that occurred, useful if "wrapping" an error to hide | ||
| * sensitive details | ||
| * @type {Error | CustomError | unknown} | ||
| */ | ||
| cause; | ||
| /** | ||
| * Further error details suitable for end user consumption | ||
| * @type {ErrorDetail[]} | ||
| */ | ||
| details; | ||
| /** | ||
| * Status code suitable to coarsely determine the reason for error | ||
| */ | ||
| code = CustomError.UNKNOWN; | ||
| /** | ||
| * Contains arbitrary debug data for developer troubleshooting | ||
| * @type {DebugData} | ||
| * @private | ||
| */ | ||
| #debug; | ||
| /** | ||
| * | ||
| * @param {string} message Developer facing message, in English. | ||
| * @param {Error | CustomError | unknown} cause | ||
| */ | ||
| constructor(message, cause) { | ||
| super(message, { cause }); | ||
| this.cause = cause; | ||
| // FF doesnt have captureStackTrace | ||
| if (Error.captureStackTrace) { | ||
| Error.captureStackTrace(this, this.constructor); | ||
| } | ||
| Object.setPrototypeOf(this, new.target.prototype); | ||
| } | ||
| static isCustomError(value) { | ||
| return !!value && typeof value === "object" && kCustomError in value; | ||
| } | ||
| /** | ||
| * Add arbitrary debug data to the error object for developer troubleshooting | ||
| */ | ||
| debug(data) { | ||
| this.#debug = withNullProto({ | ||
| ...this.#debug, | ||
| ...data, | ||
| }); | ||
| return this; | ||
| } | ||
| /** | ||
| * Adds further error details suitable for end user consumption | ||
| * @param {ErrorDetail} details | ||
| * @return {this} | ||
| */ | ||
| addDetail(...details) { | ||
| this.details = (this.details || []).concat(details); | ||
| return this; | ||
| } | ||
| /** | ||
| * A "safe" serialised version of the error designed for end user consumption | ||
| */ | ||
| toJSONSummary() { | ||
| const localised = this.details?.find((detail) => "locale" in detail); | ||
| return { | ||
| ...(localised?.message && { | ||
| message: localised.message, | ||
| }), | ||
| code: this.code, | ||
| ...(this.details && { details: this.details }), | ||
| }; | ||
| } | ||
| /** | ||
| * JSON representation of the error object that can be "hydrated" later | ||
| */ | ||
| toJSON() { | ||
| return withNullProto({ | ||
| name: this.name, | ||
| message: this.message, | ||
| code: this.code, | ||
| ...(this.details && { details: this.details }), | ||
| ...(isErrorLike(this.cause) && { | ||
| cause: "toJSON" in this.cause && typeof this.cause.toJSON === "function" | ||
| ? this.cause.toJSON() | ||
| : { | ||
| message: this.cause.message, | ||
| name: "Error", | ||
| }, | ||
| }), | ||
| ...(this.stack && { stack: this.stack }), | ||
| ...(this.#debug && { debug: this.#debug }), | ||
| }); | ||
| } | ||
| /** | ||
| * "Hydrates" a previously serialised error object | ||
| */ | ||
| static fromJSON(params) { | ||
| const { message, details, code } = params; | ||
| class ImportedError extends CustomError { | ||
| code = code; | ||
| } | ||
| const err = new ImportedError(message).debug({ | ||
| params, | ||
| }); | ||
| if (details) { | ||
| err.addDetail(...details); | ||
| } | ||
| return err; | ||
| } | ||
| /** | ||
| * An automatically determined HTTP status code | ||
| */ | ||
| static suggestHttpResponseCode(err) { | ||
| return ((CustomError.isCustomError(err) && CustomError.http[err.code]) || | ||
| CustomError.http[CustomError.UNKNOWN]); | ||
| } | ||
| } | ||
| // Mark all instances of 'CustomError' | ||
| Object.defineProperty(CustomError.prototype, kCustomError, { | ||
| value: true, | ||
| enumerable: false, | ||
| writable: false, | ||
| }); | ||
| // allow enumeration of status getter | ||
| Object.defineProperty(CustomError.prototype, "status", { | ||
| enumerable: true, | ||
| }); |
| export * from "./custom-error.js"; | ||
| export * from "./serialize.js"; | ||
| export type * from "./types.js"; |
| import { addKnownErrorConstructor } from "serialize-error"; | ||
| import { CustomError } from "./custom-error.js"; | ||
| addKnownErrorConstructor(CustomError); | ||
| export * from "./custom-error.js"; | ||
| export * from "./serialize.js"; |
| 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 }; |
| 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]); | ||
| } | ||
| return [...accum, err]; | ||
| } | ||
| return accum; | ||
| } | ||
| function recursiveSerializeError(err) { | ||
| if (CustomError.isCustomError(err)) { | ||
| const { cause, ...rest } = serializeError(err); | ||
| const causes = cause ? flatten(cause) : undefined; | ||
| return { | ||
| ...rest, | ||
| ...(causes && { | ||
| cause: causes.map(recursiveSerializeError), | ||
| }), | ||
| }; | ||
| } | ||
| if (isErrorLike(err)) { | ||
| const { name, message, stack, cause, code, ...rest } = serializeError(err); | ||
| return { | ||
| ...(name && { name }), | ||
| ...(message && { message }), | ||
| ...(code && { code }), | ||
| ...(stack && { stack }), | ||
| ...(!!cause && { cause: [recursiveSerializeError(cause)] }), | ||
| ...(rest && { debug: rest }), | ||
| }; | ||
| } | ||
| // Not an error object, maybe primitive or null, undefined | ||
| return { | ||
| name: "Error", | ||
| message: String(err), | ||
| debug: { | ||
| typeofErr: typeof err, | ||
| err: String(err), | ||
| }, | ||
| }; | ||
| } | ||
| export { recursiveSerializeError as serializeError }; |
| 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); | ||
| } |
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
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 repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
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
5
-16.67%1
-50%0
-100%10800
-38.42%7
-41.67%313
-32.25%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
Updated