Skip to content

Commit 1d267e4

Browse files
committed
feat: implement client-side caching for GitHub API responses (closes #33)
Add in-memory cache with 1-day TTL to reduce GitHub API calls. Cache keyed by username (case-insensitive) to avoid duplicate requests. Extracted reusable InMemoryCache class in lib/cache.ts with configurable TTL constants.
1 parent 60c8f15 commit 1d267e4

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

lib/cache.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
interface CacheEntry<T> {
2+
data: T;
3+
expiresAt: number;
4+
}
5+
6+
class InMemoryCache {
7+
private store = new Map<string, CacheEntry<unknown>>();
8+
9+
set<T>(key: string, data: T, ttlMs: number): void {
10+
this.store.set(key, {
11+
data,
12+
expiresAt: Date.now() + ttlMs,
13+
});
14+
}
15+
16+
get<T>(key: string): T | null {
17+
const entry = this.store.get(key) as CacheEntry<T> | undefined;
18+
if (!entry) return null;
19+
if (Date.now() > entry.expiresAt) {
20+
this.store.delete(key);
21+
return null;
22+
}
23+
return entry.data;
24+
}
25+
26+
has(key: string): boolean {
27+
return this.get(key) !== null;
28+
}
29+
30+
delete(key: string): void {
31+
this.store.delete(key);
32+
}
33+
34+
clear(): void {
35+
this.store.clear();
36+
}
37+
}
38+
39+
// Singleton cache instance shared across API requests
40+
export const cache = new InMemoryCache();
41+
42+
export const CACHE_TTL = {
43+
ONE_DAY: 24 * 60 * 60 * 1000,
44+
ONE_HOUR: 60 * 60 * 1000,
45+
FIVE_MINUTES: 5 * 60 * 1000,
46+
};

lib/github.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ContributionTotals, GitHubUserData, PullRequestNode, RepoNode } from "@/types/github";
22
import { graphql } from "@octokit/graphql";
3+
import { cache, CACHE_TTL } from "./cache";
34

45
if (!process.env.GITHUB_TOKEN) {
56
throw new Error("Missing GITHUB_TOKEN");
@@ -60,15 +61,26 @@ const QUERY = /* GraphQL */ `
6061
export async function fetchGitHubUserData(
6162
username: string
6263
): Promise<GitHubUserData> {
64+
const cacheKey = `github:user:${username.toLowerCase()}`;
65+
66+
const cached = cache.get<GitHubUserData>(cacheKey);
67+
if (cached) {
68+
return cached;
69+
}
70+
6371
const { user } = await client<{ user: any }>(QUERY, { login: username });
6472

6573
if (!user) {
6674
throw new Error("User not found");
6775
}
6876

69-
return {
77+
const data: GitHubUserData = {
7078
repos: user.repositories.nodes as RepoNode[],
7179
pullRequests: user.pullRequests.nodes as PullRequestNode[],
7280
contributions: user.contributionsCollection as ContributionTotals,
7381
};
82+
83+
cache.set(cacheKey, data, CACHE_TTL.ONE_DAY);
84+
85+
return data;
7486
}

0 commit comments

Comments
 (0)