import { isErrorID } from '@dotcom-reliability-kit/error-id'; export interface BaseErrorData { id?: string; code?: string; message?: string; cause?: unknown; [key: string]: unknown; } /** * Class representing an error. */ export default class BaseError extends Error { override readonly name: string = 'BaseError'; /** * A unique identifier for this error. */ readonly id: string; /** * Whether the error is operational. */ readonly isOperational: boolean = false; protected static readonly defaultCode: string = 'UNKNOWN'; /** * A machine-readable error code which identifies the specific type of error. */ readonly code: string = BaseError.defaultCode; /** * Additional error information. */ readonly data: { [key: string]: unknown } = {}; constructor(message?: string | BaseErrorData); constructor(message: string, data?: BaseErrorData); constructor(message: string | BaseErrorData, data: BaseErrorData = {}) { if (typeof message === 'string') { data.message = message; } else { data = message || data; } const errorOptions = data.cause ? { cause: data.cause } : {}; super(data.message || BaseError.defaultMessage, errorOptions); if (data.id && isErrorID(data.id)) { this.id = data.id; } else { this.id = BaseError.defaultId; } if (typeof data.code === 'string') { this.code = BaseError.normalizeErrorCode(data.code); } for (const [key, value] of Object.entries(data)) { const self = this.constructor as typeof BaseError; if (!self.reservedKeys.includes(key)) { this.data[key] = value; } } } /** * Reserved keys that should not appear in `BaseError.prototype.data`. */ protected static reservedKeys = ['id', 'code', 'message', 'cause']; protected static readonly defaultId: string = 'EI000'; protected static readonly defaultMessage: string = 'An error occurred'; /** * Check whether an error object is marked as operational. */ static isErrorMarkedAsOperational(error: Error) { // @ts-expect-error Error.prototype.isOperational does not exist, but it's OK to check in this // case as we're manually casting `undefined` to a Boolean return Boolean(error.isOperational); } /** * Normalize a machine-readable error code. */ private static normalizeErrorCode(code: string) { return code .trim() .toUpperCase() .replace(/[^a-z0-9_]+/gi, '_'); } }