MCPcopy Create free account
hub / github.com/codename-co/stack / fetchJSON

Function fetchJSON

packages/scripts/maintenance/lib/upstream.ts:115–190  ·  view source on GitHub ↗
(
  url: string,
  opts: FetchOptions = {}
)

Source from the content-addressed store, hash-verified

113 * gives up produces a partial diff that looks like real data loss.
114 */
115export async function fetchJSON<T = any>(
116 url: string,
117 opts: FetchOptions = {}
118): Promise<T | null> {
119 const cacheFile = join(
120 CACHE_DIR,
121 createHash("sha256").update(url).digest("hex").slice(0, 32) + ".json"
122 );
123
124 if (!opts.noCache && existsSync(cacheFile)) {
125 try {
126 const cached = JSON.parse(readFileSync(cacheFile, "utf-8"));
127 if (Date.now() - cached.at < DEFAULT_TTL_MS) return cached.body as T;
128 } catch {
129 // A corrupt cache entry is never worth failing over: refetch.
130 }
131 }
132
133 const headers: Record<string, string> = {
134 accept: "application/json",
135 "user-agent": "stack-hub-maintenance (+https://stack.lol)",
136 };
137 if (url.startsWith("https://api.github.com/")) {
138 const token = resolveGithubToken();
139 if (token) headers.authorization = `Bearer ${token}`;
140 headers.accept = "application/vnd.github+json";
141 headers["x-github-api-version"] = "2022-11-28";
142 }
143
144 let lastError = "";
145 for (let attempt = 0; attempt < 5; attempt++) {
146 let res: Response;
147 try {
148 res = await fetch(url, { headers });
149 } catch (e) {
150 lastError = String(e);
151 await sleep(1000 * 2 ** attempt);
152 continue;
153 }
154
155 if (res.ok) {
156 const body = (await res.json()) as T;
157 mkdirSync(CACHE_DIR, { recursive: true });
158 writeFileSync(cacheFile, JSON.stringify({ at: Date.now(), url, body }));
159 return body;
160 }
161
162 // 422 is GitHub's answer to "that ref does not exist" on commit lookups,
163 // and 451 is a DMCA takedown: both are answers, not failures.
164 if (res.status === 404 || res.status === 422 || res.status === 451) {
165 if (opts.allow404) return null;
166 throw new HttpError(res.status, url, `${res.status} on ${url}`);
167 }
168
169 // Primary (x-ratelimit-remaining: 0) and secondary (retry-after) limits.
170 const retryAfter = Number(res.headers.get("retry-after") ?? 0);
171 const reset = Number(res.headers.get("x-ratelimit-reset") ?? 0);
172 const remaining = res.headers.get("x-ratelimit-remaining");

Callers

nothing calls this directly

Calls 2

resolveGithubTokenFunction · 0.85
sleepFunction · 0.85

Tested by

no test coverage detected