Skip to content

Commit f28fbd2

Browse files
committed
fix: gracefully handle Algolia fetch failures
1 parent 5e4adaa commit f28fbd2

File tree

2 files changed

+54
-30
lines changed

2 files changed

+54
-30
lines changed

server/api/picks.get.ts

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,32 @@ export default defineCachedEventHandler(
3838
const { appId, apiKey, indexName } = useRuntimeConfig().public.algolia
3939

4040
// 1. Fetch popular packages from Algolia
41-
const algoliaResponse = await $fetch<AlgoliaResponse>(
42-
`https://${appId}-dsn.algolia.net/1/indexes/${indexName}/query`,
43-
{
44-
method: 'POST',
45-
headers: {
46-
'X-Algolia-Application-Id': appId,
47-
'X-Algolia-API-Key': apiKey,
41+
let algoliaHits: AlgoliaHit[] = []
42+
try {
43+
const algoliaResponse = await $fetch<AlgoliaResponse>(
44+
`https://${appId}-dsn.algolia.net/1/indexes/${indexName}/query`,
45+
{
46+
method: 'POST',
47+
headers: {
48+
'X-Algolia-Application-Id': appId,
49+
'X-Algolia-API-Key': apiKey,
50+
},
51+
body: {
52+
query: '',
53+
hitsPerPage: ALGOLIA_POOL_SIZE,
54+
attributesToRetrieve: ['name', 'downloadsLast30Days', 'isDeprecated'],
55+
attributesToHighlight: [],
56+
},
4857
},
49-
body: {
50-
query: '',
51-
hitsPerPage: ALGOLIA_POOL_SIZE,
52-
attributesToRetrieve: ['name', 'downloadsLast30Days', 'modified', 'isDeprecated'],
53-
attributesToHighlight: [],
54-
},
55-
},
56-
)
58+
)
59+
algoliaHits = algoliaResponse.hits ?? []
60+
} catch (error) {
61+
console.warn('[picks] Algolia fetch failed, falling back to empty pool', error)
62+
algoliaHits = []
63+
}
5764

5865
// 2. Post-filter pool
59-
const now = Date.now()
60-
const pool = algoliaResponse.hits.filter(hit => {
66+
const pool = algoliaHits.filter(hit => {
6167
if (hit.isDeprecated) return false
6268
if (hit.downloadsLast30Days < MIN_DOWNLOADS_LAST_30_DAYS) return false
6369
return true

test/unit/server/utils/picks.spec.ts

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ describe('selectPicks', () => {
2929
}
3030
const picks = selectPicks(candidates)
3131

32-
expect(picks[0]!.letterIndex).toBe('next'.indexOf('n'))
33-
expect(picks[1]!.letterIndex).toBe('webpack'.indexOf('p'))
34-
expect(picks[2]!.letterIndex).toBe('commander'.indexOf('m'))
35-
expect(picks[3]!.letterIndex).toBe('luxon'.indexOf('x'))
32+
expect(picks).toHaveLength(4)
33+
expect(picks[0]?.letterIndex).toBe('next'.indexOf('n'))
34+
expect(picks[1]?.letterIndex).toBe('webpack'.indexOf('p'))
35+
expect(picks[2]?.letterIndex).toBe('commander'.indexOf('m'))
36+
expect(picks[3]?.letterIndex).toBe('luxon'.indexOf('x'))
3637
})
3738

3839
it('prefers liked candidates over non-liked', () => {
@@ -61,24 +62,41 @@ describe('selectPicks', () => {
6162
x: [{ name: 'luxon', totalLikes: 0 }],
6263
}
6364
const picks = selectPicks(candidates)
64-
expect(picks[0]!.name).toBe('nodemon')
65-
expect(picks[1]!.name).toBe('prettier')
66-
expect(picks[2]!.name).toBe('mocha')
67-
expect(picks[3]!.name).toBe('luxon')
65+
expect(picks).toHaveLength(4)
66+
expect(picks[0]?.name).toBe('nodemon')
67+
expect(picks[1]?.name).toBe('prettier')
68+
expect(picks[2]?.name).toBe('mocha')
69+
expect(picks[3]?.name).toBe('luxon')
6870
})
6971

70-
it('falls back to hardcoded when no candidates at all', () => {
72+
it('falls back to hardcoded fallbacks when no candidates at all', () => {
7173
const candidates: Record<NpmxLetter, PickCandidate[]> = {
7274
n: [],
7375
p: [],
7476
m: [],
7577
x: [],
7678
}
7779
const picks = selectPicks(candidates)
78-
expect(picks[0]!.name).toBe(FALLBACKS.n)
79-
expect(picks[1]!.name).toBe(FALLBACKS.p)
80-
expect(picks[2]!.name).toBe(FALLBACKS.m)
81-
expect(picks[3]!.name).toBe(FALLBACKS.x)
80+
expect(picks).toHaveLength(4)
81+
expect(picks[0]?.name).toBe(FALLBACKS.n)
82+
expect(picks[1]?.name).toBe(FALLBACKS.p)
83+
expect(picks[2]?.name).toBe(FALLBACKS.m)
84+
expect(picks[3]?.name).toBe(FALLBACKS.x)
85+
})
86+
87+
it('falls back to hardcoded fallbacks when the request to Algolia fails', () => {
88+
const candidates: Record<NpmxLetter, PickCandidate[]> = {
89+
n: [],
90+
p: [],
91+
m: [],
92+
x: [],
93+
}
94+
const picks = selectPicks(candidates)
95+
expect(picks).toHaveLength(4)
96+
expect(picks[0]?.name).toBe(FALLBACKS.n)
97+
expect(picks[1]?.name).toBe(FALLBACKS.p)
98+
expect(picks[2]?.name).toBe(FALLBACKS.m)
99+
expect(picks[3]?.name).toBe(FALLBACKS.x)
82100
})
83101

84102
it('does not duplicate packages across picks', () => {

0 commit comments

Comments
 (0)