import { readBoundedJsonResponse } from "@rakazo/core"; import { GITHUB_API_REPO } from "./site"; export const GITHUB_STARS_TIMEOUT_MS = 5_000; export const MAX_GITHUB_STARS_RESPONSE_BYTES = 64 * 1024; export async function fetchGithubStars(fetchImpl: typeof fetch = fetch): Promise { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), GITHUB_STARS_TIMEOUT_MS); try { const response = await withAbort( fetchImpl(GITHUB_API_REPO, { headers: { Accept: "application/vnd.github+json", "User-Agent": "evoo-www", }, signal: controller.signal, }), controller.signal, ); if (!response.ok) { cancelResponseBody(response); return null; } const payload = await readBoundedJsonResponse<{ stargazers_count?: unknown; }>(response, MAX_GITHUB_STARS_RESPONSE_BYTES, controller.signal); return typeof payload.stargazers_count === "number" ? payload.stargazers_count : null; } catch { return null; } finally { clearTimeout(timer); } } function cancelResponseBody(response: Response): void { try { void Promise.resolve(response.body?.cancel()).catch(() => undefined); } catch { // Best-effort release only; cancellation must not delay static rendering. } } function withAbort(promise: Promise, signal: AbortSignal): Promise { if (signal.aborted) return Promise.reject(signal.reason ?? new Error("Request timed out")); return new Promise((resolve, reject) => { const onAbort = () => reject(signal.reason ?? new Error("Request timed out")); signal.addEventListener("abort", onAbort, { once: true }); promise.then( (value) => { signal.removeEventListener("abort", onAbort); resolve(value); }, (error: unknown) => { signal.removeEventListener("abort", onAbort); reject(error); }, ); }); } export function formatStarCount(count: number): string { if (count < 1000) { return String(count); } const thousands = count / 1000; const rounded = thousands >= 10 ? Math.round(thousands) : Math.round(thousands * 10) / 10; return `${rounded}k`.replace(/\.0k$/, "k"); }