diff --git a/src/shared/api/client.ts b/src/shared/api/client.ts index c2bd376..d936ff3 100644 --- a/src/shared/api/client.ts +++ b/src/shared/api/client.ts @@ -1,3 +1,4 @@ +import { PBHttpError } from './error'; import type { ListResponse } from './types'; /* @@ -66,7 +67,7 @@ export async function getCollection(collection: string, options: PBFetchOptio }); if (!res.ok) { - throw new Error(`PocketBase ${res.status} ${res.statusText} on collection "${collection}"`); + throw new PBHttpError(res.status, collection, res.statusText); } return res.json(); @@ -74,8 +75,20 @@ export async function getCollection(collection: string, options: PBFetchOptio /** * Fetch the first record matching an optional filter from a PocketBase collection. + * + * Returns null on connection failure (e.g. PocketBase unreachable during build) + * so prerendering doesn't crash. HTTP errors (4xx/5xx) are rethrown — PB is + * reachable but something is genuinely wrong, which shouldn't be silently hidden. */ export async function getFirstRecord(collection: string, options: PBFetchOptions = {}): Promise { - const data = await getCollection(collection, options); - return data.items[0] ?? null; + try { + const data = await getCollection(collection, options); + return data.items[0] ?? null; + } catch (err) { + if (err instanceof PBHttpError) { + throw err; + } + console.warn(`[getFirstRecord] "${collection}" unreachable — returning null`, err); + return null; + } } diff --git a/src/shared/api/error.ts b/src/shared/api/error.ts new file mode 100644 index 0000000..2a460b4 --- /dev/null +++ b/src/shared/api/error.ts @@ -0,0 +1,37 @@ +/** + * Error thrown when PocketBase responds with a non-OK HTTP status (4xx/5xx). + * + * Distinguishes *server-responded-with-failure* from *server-unreachable*. + * A connection-level failure (ECONNREFUSED, DNS, the build-time `PB_URL` + * guard) throws a plain `Error`; only an actual HTTP response throws this. + * Callers use `instanceof PBHttpError` to decide whether to swallow the + * failure (connection — safe to ignore at build) or rethrow it (HTTP — a + * real problem that must surface). + * + * @example + * try { + * await getCollection('site_settings'); + * } catch (err) { + * if (err instanceof PBHttpError) { + * // PB is up but returned e.g. 403 — log, alert, rethrow + * console.error(`PB returned ${err.status} for ${err.collection}`); + * } else { + * // PB unreachable — acceptable during build, render empty + * } + * } + */ +export class PBHttpError extends Error { + /** + * @param status HTTP status code returned by PocketBase (e.g. 404, 500). + * @param collection Name of the collection that was queried, for context. + * @param statusText HTTP status text from the response (e.g. "Not Found"). + */ + constructor( + public readonly status: number, + public readonly collection: string, + statusText: string, + ) { + super(`PocketBase ${status} ${statusText} on collection "${collection}"`); + this.name = 'PBHttpError'; + } +}