1- import { fetchCached } from '~/utils/cache.server'
2- import { graphqlWithAuth } from '~/server/github'
1+ import { fetchCachedWithStaleFallback } from '~/utils/cache.server'
2+ import {
3+ graphqlWithAuth ,
4+ isRecoverableGitHubApiError ,
5+ normalizeGitHubApiError ,
6+ } from '~/server/github'
37import { createServerFn } from '@tanstack/react-start'
48import { setResponseHeaders } from '@tanstack/react-start/server'
59import sponsorMetaData from '~/utils/gh-sponsor-meta.json'
@@ -24,15 +28,35 @@ export type Sponsor = {
2428 createdAt : string
2529}
2630
31+ const SPONSOR_CACHE_TTL_MS = process . env . NODE_ENV === 'development' ? 1 : 5 * 60 * 1000
32+
2733export const getSponsorsForSponsorPack = createServerFn ( {
2834 method : 'GET' ,
2935} ) . handler ( async ( ) => {
30- const sponsors = await fetchCached ( {
31- key : 'sponsors' ,
32- // ttl: process.env.NODE_ENV === 'development' ? 1 : 60 * 60 * 1000,
33- ttl : 60 * 1000 ,
34- fn : getSponsors ,
35- } )
36+ let sponsors : Sponsor [ ]
37+
38+ try {
39+ sponsors = await fetchCachedWithStaleFallback ( {
40+ key : 'sponsors' ,
41+ ttl : SPONSOR_CACHE_TTL_MS ,
42+ fn : getSponsors ,
43+ shouldFallbackToStale : isRecoverableGitHubApiError ,
44+ onStaleFallback : ( error ) => {
45+ console . warn ( '[getSponsorsForSponsorPack] Serving stale sponsors after GitHub failure:' , {
46+ message : error instanceof Error ? error . message : String ( error ) ,
47+ } )
48+ } ,
49+ } )
50+ } catch ( error ) {
51+ if ( isRecoverableGitHubApiError ( error ) ) {
52+ console . warn ( '[getSponsorsForSponsorPack] Falling back to metadata-only sponsors:' , {
53+ message : error . message ,
54+ } )
55+ sponsors = await getSponsorsFromMetadataOnly ( )
56+ } else {
57+ throw error
58+ }
59+ }
3660
3761 // In recent @tanstack /react-start versions, getEvent is no longer exported.
3862 // Headers can be set unconditionally here; framework will merge appropriately.
@@ -58,14 +82,12 @@ export const getSponsorsForSponsorPack = createServerFn({
5882 } ) )
5983} )
6084
61- export async function getSponsors ( ) {
62- const [ sponsors , sponsorsMeta ] = await Promise . all ( [
63- getGithubSponsors ( ) ,
64- getSponsorsMeta ( ) ,
65- ] )
66-
85+ function mergeSponsorsWithMetadata (
86+ sponsors : Sponsor [ ] ,
87+ sponsorsMeta : SponsorMeta [ ] ,
88+ ) {
6789 sponsorsMeta . forEach ( ( sponsorMeta : SponsorMeta ) => {
68- const matchingSponsor = sponsors . find ( ( d ) => d . login == sponsorMeta . login )
90+ const matchingSponsor = sponsors . find ( ( d ) => d . login === sponsorMeta . login )
6991
7092 if ( matchingSponsor ) {
7193 Object . assign ( matchingSponsor , {
@@ -95,6 +117,19 @@ export async function getSponsors() {
95117 return sponsors
96118}
97119
120+ export async function getSponsors ( ) {
121+ const [ sponsors , sponsorsMeta ] = await Promise . all ( [
122+ getGithubSponsors ( ) ,
123+ getSponsorsMeta ( ) ,
124+ ] )
125+
126+ return mergeSponsorsWithMetadata ( sponsors , sponsorsMeta )
127+ }
128+
129+ async function getSponsorsFromMetadataOnly ( ) {
130+ return mergeSponsorsWithMetadata ( [ ] , await getSponsorsMeta ( ) )
131+ }
132+
98133async function getGithubSponsors ( ) {
99134 let sponsors : Sponsor [ ] = [ ]
100135 try {
@@ -205,35 +240,8 @@ async function getGithubSponsors() {
205240 await fetchPage ( )
206241
207242 return sponsors
208- } catch ( err ) {
209- const error = err as { status ?: number }
210- if ( error . status === 401 ) {
211- console . error ( 'Missing github credentials, returning mock data.' )
212- return [
213- 'tannerlinsley' ,
214- 'tkdodo' ,
215- 'crutchcorn' ,
216- 'kevinvandy' ,
217- 'jherr' ,
218- 'seancassiere' ,
219- 'schiller-manuel' ,
220- ] . flatMap ( ( d ) =>
221- new Array ( 20 ) . fill ( d ) . map ( ( _ , i2 ) => ( {
222- login : d ,
223- name : d ,
224- amount : ( 20 - i2 ) / 20 + Math . random ( ) ,
225- createdAt : new Date ( ) . toISOString ( ) ,
226- private : false ,
227- linkUrl : `https://github.com/${ d } ` ,
228- imageUrl : `https://github.com/${ d } .png` ,
229- } ) ) ,
230- )
231- }
232- if ( error . status === 403 ) {
233- console . error ( 'GitHub rate limit exceeded, returning empty sponsors.' )
234- return [ ]
235- }
236- throw err
243+ } catch ( error ) {
244+ throw normalizeGitHubApiError ( error , 'Fetching GitHub sponsors' )
237245 }
238246}
239247
0 commit comments