/** A utility to get the type of an error, particularly * useful for the purpose of error deduplication * * Note: prefers 'code' over 'name' in the expectation that this better * represents the error type */ const isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.trim().length > 0; export function getErrorType(error: unknown): string { if (error && typeof error === 'object') { const o = error as { code?: unknown; name?: unknown; constructor?: { name?: unknown } }; if (isNonEmptyString(o.code)) return o.code; if (isNonEmptyString(o.name)) return o.name; const ctorName = o.constructor?.name; if (isNonEmptyString(ctorName) && ctorName !== 'Object') return ctorName; } return 'Error'; } export interface ErrorSummary { type: string; name?: string; message: string; code?: string; status?: number; stack?: string; } /** * Produce a compact, log-friendly summary of an unknown error. * * Intentionally excludes large payload fields (e.g. `data`, `response`, etc.) to avoid redundant * logging when inner layers already emitted detailed request/response diagnostics. */ export function summarizeError(error: unknown): ErrorSummary { const summary: ErrorSummary = { type: getErrorType(error), message: error instanceof Error ? error.message : String(error), }; if (error instanceof Error) { summary.name = error.name; summary.stack = error.stack; } if (error && typeof error === 'object') { const o = error as any; if (isNonEmptyString(o.name)) summary.name = o.name; if (isNonEmptyString(o.message)) summary.message = o.message; if (isNonEmptyString(o.code)) summary.code = o.code; if (typeof o.status === 'number') summary.status = o.status; if (typeof o.httpStatus === 'number') summary.status = o.httpStatus; // Common axios-like shape if (typeof o.response?.status === 'number') summary.status = o.response.status; } return summary; }