Feed core — error handling
The core throws exactly one error type, GhostwritrError, for every failure — config, network, HTTP, and contract violations. It’s always catchable and inspectable. For the cross-SDK model, see error handling.
GhostwritrError
Section titled “GhostwritrError”class GhostwritrError extends Error { readonly status: number; // HTTP status, or 0 for client-side/config/network readonly code: GhostwritrErrorCode | null; readonly details?: unknown; // the offending URL or raw payload, for debugging}instanceof GhostwritrError works across transpile targets. Branch on code, not on the message.
import { fetchArticles, GhostwritrError } from "@ghostwritr/feed";
try { const articles = await fetchArticles({ siteId });} catch (err) { if (err instanceof GhostwritrError && err.code === "NOT_FOUND") { // This site has no feed yet — render an empty state. } else { throw err; // unexpected — let it surface }}The code union
Section titled “The code union”GhostwritrErrorCode:
| code | when | status |
|---|---|---|
CONFIG | bad input — missing siteId/slug, invalid staticBaseUrl | 0 |
NETWORK_ERROR | the underlying fetch rejected (offline, DNS, TLS) | 0 |
NOT_FOUND | the feed manifest is absent — the site has no feed | 404 |
INVALID_RESPONSE | the feed returned non-JSON or a malformed manifest/article/timestamp | 0 or HTTP |
UNAUTHORIZED | HTTP 401 from the origin | 401 |
FORBIDDEN | HTTP 403 from the origin | 403 |
RATE_LIMITED | HTTP 429 from the origin | 429 |
SERVER_ERROR | HTTP 5xx (or an unmapped status) from the origin | ≥500 |
The type is ... | (string & {}), so the union stays open to future codes — handle a default branch.
Fail closed
Section titled “Fail closed”Reads fail closed: there is no API key and no authed fallback, so a read either succeeds against the static feed or throws. The two contract codes to know:
NOT_FOUND— the manifest is missing, so the site has no feed. This is the “un-backfilled site” signal.fetchArticlesthrows it; it does not return[]. (An empty but present feed returns[]. A missing article fromfetchArticlereturnsnull. Only a missing feed throws.)INVALID_RESPONSE— the feed responded, but the bytes violate the contract: non-JSON, a malformed manifest, an article missing a guaranteed field, or an unparseable timestamp. A published article is a complete article, so this is never silently coerced.
What to reach for next
Section titled “What to reach for next”- The concept — the cross-SDK error contract. See Error handling.
- Inject resilience — the
opts.fetchseam. See Fetchers. - Full surface — every export, typed. See API reference.