# @openstatus/status-fetcher Effect-based fetchers for third-party status pages. Each fetcher returns an `Effect.Effect` so failures propagate through the typed error channel and successful results carry a normalized shape across providers. ## Quick start ```ts import { Effect } from "effect"; import { FetchError, fetchers } from "@openstatus/status-fetcher"; import type { StatusPageEntry } from "@openstatus/status-fetcher"; const entry: StatusPageEntry = { id: "github", name: "GitHub", url: "https://github.com", status_page_url: "https://www.githubstatus.com", provider: "atlassian-statuspage", industry: ["development-tools"], api_config: { type: "atlassian" }, }; const fetcher = fetchers.find((f) => f.canHandle(entry)); if (!fetcher) throw new Error("no fetcher matches entry"); const result = await Effect.runPromise(fetcher.fetch(entry)); console.log(result.severity, result.status, result.description); ``` For batch fan-out with per-item success/failure, use `Effect.forEach` + `Effect.either`: ```ts import { Effect, Either } from "effect"; const results = await Effect.runPromise( Effect.forEach( entries, (entry) => { const fetcher = fetchers.find((f) => f.canHandle(entry)); if (!fetcher) { return Effect.either( Effect.fail( new FetchError({ url: entry.status_page_url, entryId: entry.id }), ), ); } return fetcher.fetch(entry).pipe(Effect.either); }, { concurrency: "unbounded" }, ), ); for (const r of results) { if (Either.isRight(r)) { /* r.right is the StatusResult */ } else { /* r.left is a FetchError */ } } ``` ## Types ### `StatusPageEntry` ```ts interface StatusPageEntry { id: string; name: string; url: string; status_page_url: string; provider: StatusPageProvider; industry: Industry[]; description?: string; api_config?: ApiConfig; } ``` ### `StatusResult` ```ts interface StatusResult { severity: SeverityLevel; // "none" | "minor" | "major" | "critical" status: StatusType; // "operational" | "degraded" | "partial_outage" | // "major_outage" | "under_maintenance" | // "investigating" | "identified" | // "monitoring" | "resolved" description: string; updated_at: number; // ms since epoch timezone?: string; } ``` ### `FetchError` Thrown via `Effect.fail` on any fetch failure. Always carries `url`; carries `fetcherName` / `entryId` / `httpStatus` / `cause` when available. ```ts class FetchError extends Error { readonly url: string; readonly fetcherName?: string; readonly entryId?: string; readonly httpStatus?: number; readonly kind?: "http" | "parse" | "schema" | "network" | "timeout"; // `.cause: unknown` (inherited from Error) } ``` `kind` classifies the failure: `http` (non-2xx), `parse` (body is not JSON), `schema` (JSON that the fetcher's zod schema rejects), `network` (fetch threw) or `timeout`. The computed `.message` is `[ ()]