Skip to content

Commit 6e49401

Browse files
committed
feat: progressive loading for org packages
Instead of capping at 1000 packages and blocking on all metadata, load the first 250 immediately and fetch the rest on demand. Filtering/sorting triggers loadAll() so client-side search still works on the full set.
1 parent 824cba3 commit 6e49401

File tree

5 files changed

+275
-87
lines changed

5 files changed

+275
-87
lines changed

app/composables/npm/useAlgoliaSearch.ts

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,33 @@ export function useAlgoliaSearch() {
222222
}
223223
}
224224

225+
/** Fetch metadata for a single batch of packages (max 1000) by exact name. */
226+
async function getPackagesByNameSlice(names: string[]): Promise<NpmSearchResult[]> {
227+
if (names.length === 0) return []
228+
229+
const response = await $fetch<{ results: (AlgoliaHit | null)[] }>(
230+
`https://${algolia.appId}-dsn.algolia.net/1/indexes/*/objects`,
231+
{
232+
method: 'POST',
233+
headers: {
234+
'x-algolia-api-key': algolia.apiKey,
235+
'x-algolia-application-id': algolia.appId,
236+
},
237+
body: {
238+
requests: names.map(name => ({
239+
indexName,
240+
objectID: name,
241+
attributesToRetrieve: ATTRIBUTES_TO_RETRIEVE,
242+
})),
243+
},
244+
},
245+
)
246+
247+
return response.results
248+
.filter((r): r is AlgoliaHit => r !== null && 'name' in r)
249+
.map(hitToSearchResult)
250+
}
251+
225252
/** Fetch metadata for specific packages by exact name using Algolia's getObjects API. */
226253
async function getPackagesByName(packageNames: string[]): Promise<NpmSearchResponse> {
227254
if (packageNames.length === 0) {
@@ -230,36 +257,18 @@ export function useAlgoliaSearch() {
230257

231258
// Algolia getObjects has a limit of 1000 objects per request, so batch if needed
232259
const BATCH_SIZE = 1000
233-
const allHits: AlgoliaHit[] = []
234-
260+
const batches: string[][] = []
235261
for (let i = 0; i < packageNames.length; i += BATCH_SIZE) {
236-
const batch = packageNames.slice(i, i + BATCH_SIZE)
237-
const response = await $fetch<{ results: (AlgoliaHit | null)[] }>(
238-
`https://${algolia.appId}-dsn.algolia.net/1/indexes/*/objects`,
239-
{
240-
method: 'POST',
241-
headers: {
242-
'x-algolia-api-key': algolia.apiKey,
243-
'x-algolia-application-id': algolia.appId,
244-
},
245-
body: {
246-
requests: batch.map(name => ({
247-
indexName,
248-
objectID: name,
249-
attributesToRetrieve: ATTRIBUTES_TO_RETRIEVE,
250-
})),
251-
},
252-
},
253-
)
254-
255-
const hits = response.results.filter((r): r is AlgoliaHit => r !== null && 'name' in r)
256-
allHits.push(...hits)
262+
batches.push(packageNames.slice(i, i + BATCH_SIZE))
257263
}
258264

265+
const results = await Promise.all(batches.map(batch => getPackagesByNameSlice(batch)))
266+
const allObjects = results.flat()
267+
259268
return {
260269
isStale: false,
261-
objects: allHits.map(hitToSearchResult),
262-
total: allHits.length,
270+
objects: allObjects,
271+
total: allObjects.length,
263272
time: new Date().toISOString(),
264273
}
265274
}
@@ -366,5 +375,6 @@ export function useAlgoliaSearch() {
366375
searchWithSuggestions,
367376
searchByOwner,
368377
getPackagesByName,
378+
getPackagesByNameSlice,
369379
}
370380
}

0 commit comments

Comments
 (0)