@cogs/fetch-error-handler
v0.2.0
Published
Properly handle fetch errors and avoid a lot of boilerplate in your app.
Readme
@cogs/fetch-error-handler
Cross-runtime fetch guard that turns ambiguous upstream failures into structured @cogs/errors. Ship the same handler to Node.js (Next.js / workers) and browsers, drain unread response bodies, and remove bespoke boilerplate wherever you call fetch.
Features
- One API, two runtimes – conditional exports surface the Node implementation by default (
create-handler.node.ts) and the browser implementation to bundlers via thebrowserfield. Both share the same logic, differing only in how response bodies are drained. - Structured error mapping – HTTP 4xx/5xx, DNS lookup issues, socket hangups, aborted or timed-out requests, and invalid JSON payloads are promoted to
HttpError,OperationalError, orUpstreamServiceErrorwith normalizedcodes andrelatesToSystems. - Response body insight without leaks – the handler clones responses, truncates large payloads (2 KB cap), and records raw text/JSON in the thrown error. The original
Responsebody is drained via environment hooks so Node streams and browser readers do not linger. - Promise-aware wrapper – accept either a
Responseor aPromise<Response>; any rejection is re-thrown as a richer error and successful responses must pass.ok/.statuschecks before returning. - Tiny surface area –
createFetchErrorHandler(options?)returns ahandleFetchErrors(responseOrPromise)helper. A ready-to-use singleton (handleFetchErrors) is exported for quick integration.
Installation
pnpm add @cogs/fetch-error-handlerUsage
JavaScript
import { handleFetchErrors } from "@cogs/fetch-error-handler"
export async function loadFeatureFlags() {
const response = await handleFetchErrors(
fetch("https://example.internal/api/flags", { method: "GET" }),
)
return response.json()
}TypeScript (custom handler per upstream)
import { createFetchErrorHandler } from "@cogs/fetch-error-handler"
const handleUpstreamErrors = createFetchErrorHandler({
upstreamSystemCode: "UPSTREAM_API",
})
export async function callUpstream<T>(input: RequestInfo, init?: RequestInit): Promise<T> {
const response = await handleUpstreamErrors(fetch(input, init))
return response.json() as Promise<T>
}Wiring into other clients
Wrap the underlying fetch Promise from any XHR-like abstraction:
import { handleFetchErrors } from "@cogs/fetch-error-handler"
const safeFetch = (input: RequestInfo, init?: RequestInit) =>
handleFetchErrors(fetch(input, init))
// inside a client
const response = await safeFetch(url, {
method: "POST",
body: JSON.stringify(payload),
})Options
upstreamSystemCode?: string– when provided, the resulting errors will includerelatesToSystems: [upstreamSystemCode], helping log aggregation and alerting pipelines bucket failures.
Error Mapping Summary
- HTTP 4xx →
HttpErrorwithcode: "FETCH_CLIENT_ERROR"(status coerced to 500 for consistent downstream handling). - HTTP 5xx →
UpstreamServiceErrorwithcode: "FETCH_SERVER_ERROR". - Unexpected status (non-304, non-ok) →
HttpErrorcode: "FETCH_UNKNOWN_ERROR". - Invalid JSON in OK response →
UpstreamServiceErrorcode: "FETCH_INVALID_JSON_ERROR". - DNS / ENOTFOUND →
OperationalErrorcode: "FETCH_DNS_LOOKUP_ERROR". - Abort / timeout →
OperationalErrorcode: "FETCH_ABORT_ERROR"orFETCH_TIMEOUT_ERROR. - Socket hangup / ECONNRESET →
UpstreamServiceErrorcode: "FETCH_SOCKET_HANGUP_ERROR". - Any other thrown
Errorpasses through untouched so higher layers can decide whether to crash or recover.
Scripts
pnpm build– compile ESM output and declarations viatsc.pnpm typecheck– type-check without emitting.pnpm lint– run Biome.pnpm test– run the Vitest suite (unit + end-to-end fixtures).
