Core API
Everything in @harshilrajput/universal-api-errors-core — re-exported by every adapter.
The core package has zero runtime dependencies, works standalone via duck-typing, and is re-exported in full by both the Axios and fetch adapters — so everything below is available whichever package you install.
parseError
function parseError(input: unknown, options?: { source?: string }): UniversalErrorThe generic parser. Recognizes Axios/ky/superagent's { response: { status, data } } shape,
Node's error codes (ECONNREFUSED, ETIMEDOUT, ...), and the browser fetch network-failure
message family — all without importing any of those libraries.
import { parseError } from "@harshilrajput/universal-api-errors-core";
const error = parseError(err);createUniversalError
function createUniversalError(input: ParsedErrorInput): UniversalErrorBuilds a fully-classified UniversalError from partial adapter output (message, status,
code, validation, ...). This is what every adapter calls once it has extracted those fields
from its client's native error shape — it derives type, retryable, and every isX flag
consistently, so that logic lives in one place instead of being copy-pasted into every adapter.
The UniversalError class
UniversalError extends the built-in Error — not a plain object — so throw,
instanceof Error, console.error, and error-reporting SDKs keep working exactly as they
already do.
interface UniversalError extends Error {
message: string;
code?: string;
status?: number;
type: ErrorType;
retryable: boolean;
validation?: { [field: string]: string[] };
suggestion?: string;
docs?: string;
source: string;
original: unknown;
isUnauthorized: boolean;
isForbidden: boolean;
isNotFound: boolean;
isValidation: boolean;
isRateLimited: boolean;
isServer: boolean;
isNetwork: boolean;
isOffline: boolean;
isTimeout: boolean;
isCancelled: boolean;
isDNS: boolean;
isSSL: boolean;
isCors: boolean;
shouldRetry(predicate?: (error: UniversalError) => boolean): boolean;
debug(): void;
log(): void;
toJSON(): Record<string, unknown>;
}See Error types for what each type and flag actually means.
retry
function retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>import { retry } from "@harshilrajput/universal-api-errors-core";
const user = await retry(() => fetchUser(id), {
retries: 3, // default: 3
delay: "exponential", // "fixed", "exponential", or a number of ms — default: "exponential"
jitter: true, // default: true — full jitter, avoids thundering-herd retries
factor: 2, // exponential multiplier per attempt — default: 2
maxDelay: 30_000, // upper bound on any computed delay — default: 30s
shouldRetry: (error, attempt) => true, // decide whether to retry a given error
onRetry: (error, attempt, delayMs) => {}, // called before each retry sleep
});computeRetryDelay(attempt, options) is also exported standalone if you want the same backoff
curve without the retry loop — it's what UniversalError.shouldRetry() and retry() both use
internally.
normalizeValidation
function normalizeValidation(input: unknown): { [field: string]: string[] } | undefinedFolds every validation-error shape this ecosystem produces into one { field: string[] } map:
import { normalizeValidation } from "@harshilrajput/universal-api-errors-core";
normalizeValidation({ email: ["Already exists"] }); // Laravel / Rails
normalizeValidation([{ path: "email", msg: "Already exists" }]); // express-validator
normalizeValidation([{ loc: ["body", "email"], msg: "field required" }]); // FastAPI
// => { email: ["Already exists" | "field required"] } in every caseLogging
function configureLogger(config: LoggerConfig): void
function redact(value: unknown): unknown
function formatDebug(error: UniversalError): stringimport { configureLogger } from "@harshilrajput/universal-api-errors-core";
configureLogger({
redactKeys: ["password", "token", "ssn"], // extends the built-in secret-key list
redactWith: "[REDACTED]", // default replacement value
format: "json", // force json instead of auto (pretty outside NODE_ENV=production)
sink: (payload) => myLogger.error(payload), // route to pino/winston/etc. instead of console
});error.debug() prints a readable block (status, type, message, source, suggestion); error.log()
emits structured output through the same config, redacting secret-looking keys in original by
default.
Classification helpers
Lower-level building blocks, exported for adapters and advanced use:
function statusToType(status: number | undefined): ErrorType
function resolveErrorType(status: number | undefined, code: string | undefined, message: string | undefined): ErrorType
function isRetryableByDefault(type: ErrorType, status: number | undefined): boolean
function deriveFlags(input: FlagInput): UniversalErrorFlagsThese are what every adapter calls internally — statusToType maps an HTTP status to a coarse
ErrorType, resolveErrorType falls back to Node error codes / message sniffing when there's
no status at all, and deriveFlags derives the full isX flag set consistently.