Skip to content

Commit b936980

Browse files
committed
Use GITHUB_API_TOKEN if available (docs/.env, env var). Improve error handling
1 parent 2328a54 commit b936980

2 files changed

Lines changed: 114 additions & 18 deletions

File tree

docs/src/lib/api.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { parse } from '@layerstack/utils';
2+
3+
export type ApiOptions = {
4+
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
5+
data?: any;
6+
headers?: Record<string, string>;
7+
fetch?: typeof globalThis.fetch;
8+
parse?<T>(data: string): T;
9+
};
10+
11+
export async function api<Data = any>(
12+
origin: string,
13+
resource: string,
14+
options: ApiOptions = {}
15+
): Promise<Data | null> {
16+
let url = `${origin}/${resource}`;
17+
const method = options?.method ?? 'GET';
18+
const _fetch = options?.fetch ?? globalThis.fetch;
19+
20+
if (method === 'GET' && options?.data) {
21+
url += `?${new URLSearchParams(options.data)}`;
22+
}
23+
24+
const response = await _fetch(url, {
25+
method,
26+
headers: {
27+
'Content-Type': 'application/json',
28+
...options.headers
29+
},
30+
...(method === 'POST' &&
31+
options?.data && {
32+
body: JSON.stringify(options.data)
33+
})
34+
});
35+
36+
const text = await response.text();
37+
38+
if (!response.ok) {
39+
console.error(`API ${method} ${url} failed: ${response.status} ${response.statusText} - ${text}`);
40+
return null;
41+
}
42+
43+
try {
44+
return options.parse ? options.parse<Data>(text) : parse<Data>(text);
45+
} catch {
46+
console.error(`API ${method} ${url} returned invalid JSON: ${text.slice(0, 200)}`);
47+
return null;
48+
}
49+
}
50+
51+
export async function graphql<Data = any>(
52+
endpoint: string,
53+
query: string,
54+
variables: Record<string, any> = {},
55+
options: ApiOptions = {}
56+
): Promise<Data | null> {
57+
const _fetch = options?.fetch ?? globalThis.fetch;
58+
59+
const response = await _fetch(endpoint, {
60+
method: options?.method ?? 'POST',
61+
headers: {
62+
'Content-Type': 'application/json',
63+
...options.headers
64+
},
65+
body: JSON.stringify({
66+
query,
67+
variables,
68+
...options.data
69+
})
70+
});
71+
72+
const text = await response.text();
73+
74+
if (!response.ok) {
75+
console.error(`GraphQL ${endpoint} failed: ${response.status} ${response.statusText} - ${text}`);
76+
return null;
77+
}
78+
79+
try {
80+
const json = options.parse ? options.parse(text) : parse(text);
81+
return json.data as Data;
82+
} catch {
83+
console.error(`GraphQL ${endpoint} returned invalid JSON: ${text.slice(0, 200)}`);
84+
return null;
85+
}
86+
}

docs/src/lib/stats.remote.ts

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,39 @@
11
import { query, getRequestEvent } from '$app/server';
2+
import { env } from '$env/dynamic/private';
3+
import { api } from './api';
24

35
export const getStats = query(async () => {
46
const { fetch } = getRequestEvent();
5-
const [githubRes, npmWeeklyRes, npmMonthlyRes, npmLifetimeRes, discordRes, bskyRes] =
7+
8+
const githubHeaders: Record<string, string> = { Accept: 'application/vnd.github.v3+json' };
9+
if (env.GITHUB_API_TOKEN) {
10+
const prefix = env.GITHUB_API_TOKEN.startsWith('ghp_') ? 'token' : 'Bearer';
11+
githubHeaders['Authorization'] = `${prefix} ${env.GITHUB_API_TOKEN}`;
12+
}
13+
14+
const [githubData, npmWeeklyData, npmMonthlyData, npmLifetimeData, discordData, bskyData] =
615
await Promise.all([
7-
fetch('https://api.github.com/repos/techniq/layerchart', {
8-
headers: { Accept: 'application/vnd.github.v3+json' }
16+
api('https://api.github.com', 'repos/techniq/layerchart', {
17+
fetch,
18+
headers: githubHeaders
19+
}),
20+
api('https://api.npmjs.org', 'downloads/point/last-week/layerchart', { fetch }),
21+
api('https://api.npmjs.org', 'downloads/point/last-month/layerchart', { fetch }),
22+
api('https://api.npmjs.org', 'downloads/point/2020-01-01:2099-12-31/layerchart', {
23+
fetch
924
}),
10-
fetch('https://api.npmjs.org/downloads/point/last-week/layerchart'),
11-
fetch('https://api.npmjs.org/downloads/point/last-month/layerchart'),
12-
fetch('https://api.npmjs.org/downloads/point/2020-01-01:2099-12-31/layerchart'),
13-
fetch('https://discord.com/api/v9/invites/697JhMPD3t?with_counts=true'),
14-
fetch('https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=techniq.dev')
25+
api('https://discord.com', 'api/v9/invites/697JhMPD3t?with_counts=true', { fetch }),
26+
api('https://public.api.bsky.app', 'xrpc/app.bsky.actor.getProfile?actor=techniq.dev', {
27+
fetch
28+
})
1529
]);
1630

17-
const githubStars = githubRes.ok ? ((await githubRes.json()).stargazers_count as number) : null;
18-
const npmWeekly = npmWeeklyRes.ok ? ((await npmWeeklyRes.json()).downloads as number) : null;
19-
const npmMonthly = npmMonthlyRes.ok ? ((await npmMonthlyRes.json()).downloads as number) : null;
20-
const npmLifetime = npmLifetimeRes.ok
21-
? ((await npmLifetimeRes.json()).downloads as number)
22-
: null;
23-
const bskyFollowers = bskyRes.ok ? ((await bskyRes.json()).followersCount as number) : null;
24-
const discordMembers = discordRes.ok
25-
? ((await discordRes.json()).approximate_member_count as number)
26-
: null;
31+
const githubStars = (githubData?.stargazers_count as number) ?? null;
32+
const npmWeekly = (npmWeeklyData?.downloads as number) ?? null;
33+
const npmMonthly = (npmMonthlyData?.downloads as number) ?? null;
34+
const npmLifetime = (npmLifetimeData?.downloads as number) ?? null;
35+
const bskyFollowers = (bskyData?.followersCount as number) ?? null;
36+
const discordMembers = (discordData?.approximate_member_count as number) ?? null;
2737

2838
const npmDownloads: [number | null, number | null, number | null] = [
2939
npmWeekly,

0 commit comments

Comments
 (0)