@rmdes/indiekit-endpoint-github 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -109,16 +109,16 @@ export const activityController = {
109
109
  activity = utils.extractRepoActivity(events, username);
110
110
  }
111
111
  } catch (apiError) {
112
- return response
113
- .status(apiError.status || 500)
114
- .json({ error: apiError.message });
112
+ console.error("[Activity] API error:", apiError.message);
113
+ return response.json({ activity: [], error: "GitHub API temporarily unavailable" });
115
114
  }
116
115
 
117
116
  activity = activity.slice(0, limits.activity);
118
117
 
119
118
  response.json({ activity });
120
119
  } catch (error) {
121
- next(error);
120
+ console.error("[Activity] Controller error:", error.message);
121
+ response.json({ activity: [], error: "GitHub API temporarily unavailable" });
122
122
  }
123
123
  },
124
124
  };
@@ -206,7 +206,16 @@ export const changelogController = {
206
206
  generatedAt: new Date().toISOString(),
207
207
  });
208
208
  } catch (error) {
209
- next(error);
209
+ console.error("[Changelog] API error:", error.message);
210
+ response.json({
211
+ commits: [],
212
+ categories: {},
213
+ commitCategories: {},
214
+ totalCommits: 0,
215
+ days: daysValue || 30,
216
+ generatedAt: new Date().toISOString(),
217
+ error: "GitHub API temporarily unavailable",
218
+ });
210
219
  }
211
220
  },
212
221
  };
@@ -69,16 +69,16 @@ export const starsController = {
69
69
  try {
70
70
  starred = await client.getUserStarred(username, limits.stars);
71
71
  } catch (apiError) {
72
- return response
73
- .status(apiError.status || 500)
74
- .json({ error: apiError.message });
72
+ console.error("[Stars] API error:", apiError.message);
73
+ return response.json({ stars: [], error: "GitHub API temporarily unavailable" });
75
74
  }
76
75
 
77
76
  const stars = utils.formatStarred(starred);
78
77
 
79
78
  response.json({ stars });
80
79
  } catch (error) {
81
- next(error);
80
+ console.error("[Stars] Controller error:", error.message);
81
+ response.json({ stars: [], error: "GitHub API temporarily unavailable" });
82
82
  }
83
83
  },
84
84
  };
@@ -22,7 +22,7 @@ export class GitHubClient {
22
22
  async fetch(endpoint) {
23
23
  const url = `${BASE_URL}${endpoint}`;
24
24
 
25
- // Check cache first
25
+ // Check cache first — return fresh data immediately
26
26
  const cached = this.cache.get(url);
27
27
  if (cached && Date.now() - cached.timestamp < this.cacheTtl) {
28
28
  return cached.data;
@@ -37,28 +37,40 @@ export class GitHubClient {
37
37
  headers.Authorization = `Bearer ${this.token}`;
38
38
  }
39
39
 
40
- const response = await fetch(url, { headers });
41
-
42
- if (!response.ok) {
43
- // Only use fromFetch for JSON error responses; GitHub sometimes returns
44
- // HTML error pages (e.g., 502 Bad Gateway) which cause SyntaxError noise
45
- const contentType = response.headers.get("content-type") || "";
46
- if (contentType.includes("json")) {
47
- throw await IndiekitError.fromFetch(response);
40
+ try {
41
+ const response = await fetch(url, { headers });
42
+
43
+ if (!response.ok) {
44
+ // Only use fromFetch for JSON error responses; GitHub sometimes returns
45
+ // HTML error pages (e.g., 502 Bad Gateway) which cause SyntaxError noise
46
+ const contentType = response.headers.get("content-type") || "";
47
+ if (contentType.includes("json")) {
48
+ throw await IndiekitError.fromFetch(response);
49
+ }
50
+
51
+ throw new IndiekitError(response.statusText, {
52
+ status: response.status,
53
+ code: response.statusText,
54
+ });
48
55
  }
49
56
 
50
- throw new IndiekitError(response.statusText, {
51
- status: response.status,
52
- code: response.statusText,
53
- });
54
- }
57
+ const data = await response.json();
55
58
 
56
- const data = await response.json();
59
+ // Cache result
60
+ this.cache.set(url, { data, timestamp: Date.now() });
57
61
 
58
- // Cache result
59
- this.cache.set(url, { data, timestamp: Date.now() });
62
+ return data;
63
+ } catch (error) {
64
+ // Stale-while-error: if we have stale cached data, return it
65
+ if (cached) {
66
+ console.warn(
67
+ `[GitHub] API error for ${endpoint}: ${error.message}. Serving stale cache (age: ${Math.round((Date.now() - cached.timestamp) / 60_000)}min)`,
68
+ );
69
+ return cached.data;
70
+ }
60
71
 
61
- return data;
72
+ throw error;
73
+ }
62
74
  }
63
75
 
64
76
  /**
@@ -86,21 +86,20 @@ export async function annotateWithLists(db, lists) {
86
86
  }
87
87
  }
88
88
 
89
- // Single ordered bulkWrite: reset all, then set per-repo lists
90
- const ops = [
91
- { updateMany: { filter: {}, update: { $set: { lists: [] } } } },
92
- ...[...repoToLists.entries()].map(([fullName, slugs]) => ({
93
- updateOne: {
94
- filter: { fullName },
95
- update: { $set: { lists: slugs } },
96
- },
97
- })),
98
- ];
89
+ // Reset all repos' lists first, then annotate per-repo
90
+ await collection.updateMany({}, { $set: { lists: [] } });
91
+
92
+ const annotateOps = [...repoToLists.entries()].map(([fullName, slugs]) => ({
93
+ updateOne: {
94
+ filter: { fullName },
95
+ update: { $set: { lists: slugs } },
96
+ },
97
+ }));
99
98
 
100
99
  // Process in batches of 1000 to avoid MongoDB limits
101
100
  let reposAnnotated = 0;
102
- for (let i = 0; i < ops.length; i += 1000) {
103
- const batch = ops.slice(i, i + 1000);
101
+ for (let i = 0; i < annotateOps.length; i += 1000) {
102
+ const batch = annotateOps.slice(i, i + 1000);
104
103
  const result = await collection.bulkWrite(batch, { ordered: true });
105
104
  reposAnnotated += result.modifiedCount;
106
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-github",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "GitHub activity endpoint for Indiekit. Display commits, stars, contributions, and featured repositories.",
5
5
  "keywords": [
6
6
  "indiekit",