1- import type { NpmSearchResponse , NpmSearchResult } from '#shared/types'
2- import type { SearchProvider } from '~/composables/useSettings'
1+ import type { NpmSearchResponse , NpmSearchResult , SearchProvider } from '#shared/types'
32import type { AlgoliaMultiSearchChecks } from './useAlgoliaSearch'
43import { type SearchSuggestion , emptySearchResponse , parseSuggestionIntent } from './search-utils'
54import { isValidNewPackageName , checkPackageExists } from '~/utils/package-name'
65
6+ export const SEARCH_ENGINE_HITS_LIMIT : Record < SearchProvider , number > = {
7+ algolia : 1000 ,
8+ npm : 5000 ,
9+ } as const
10+
711function emptySearchPayload ( ) {
812 return {
913 searchResponse : emptySearchResponse ( ) ,
@@ -25,6 +29,14 @@ export interface UseSearchConfig {
2529 suggestions ?: boolean
2630}
2731
32+ interface SearchResponseCache {
33+ query : string
34+ provider : SearchProvider
35+ objects : NpmSearchResult [ ]
36+ totalUnlimited : number
37+ total : number
38+ }
39+
2840export function useSearch (
2941 query : MaybeRefOrGetter < string > ,
3042 searchProvider : MaybeRefOrGetter < SearchProvider > ,
@@ -38,12 +50,7 @@ export function useSearch(
3850 checkUserExists : checkUserNpm ,
3951 } = useNpmSearch ( )
4052
41- const cache = shallowRef < {
42- query : string
43- provider : SearchProvider
44- objects : NpmSearchResult [ ]
45- total : number
46- } | null > ( null )
53+ const cache = shallowRef < SearchResponseCache | null > ( null )
4754
4855 const isLoadingMore = shallowRef ( false )
4956 const isRateLimited = shallowRef ( false )
@@ -54,6 +61,23 @@ export function useSearch(
5461 const existenceCache = shallowRef < Record < string , boolean > > ( { } )
5562 const suggestionRequestId = shallowRef ( 0 )
5663
64+ function setCache ( objects : NpmSearchResult [ ] | null , total : number = 0 ) : void {
65+ if ( objects === null ) {
66+ cache . value = null
67+ return
68+ }
69+
70+ const provider = toValue ( searchProvider )
71+
72+ cache . value = {
73+ query : toValue ( query ) ,
74+ provider,
75+ objects,
76+ totalUnlimited : total ,
77+ total : Math . min ( total , SEARCH_ENGINE_HITS_LIMIT [ provider ] ) ,
78+ }
79+ }
80+
5781 /**
5882 * Determine which extra checks to include in the Algolia multi-search.
5983 * Returns `undefined` when nothing uncached needs checking.
@@ -154,7 +178,7 @@ export function useSearch(
154178 }
155179
156180 const opts = toValue ( options )
157- cache . value = null
181+ setCache ( null )
158182
159183 if ( provider === 'algolia' ) {
160184 const checks = config . suggestions ? buildAlgoliaChecks ( q ) : undefined
@@ -197,12 +221,7 @@ export function useSearch(
197221 return emptySearchPayload ( )
198222 }
199223
200- cache . value = {
201- query : q ,
202- provider,
203- objects : response . objects ,
204- total : response . total ,
205- }
224+ setCache ( response . objects , response . total )
206225
207226 isRateLimited . value = false
208227 return {
@@ -230,25 +249,20 @@ export function useSearch(
230249 const provider = toValue ( searchProvider )
231250
232251 if ( ! q ) {
233- cache . value = null
252+ setCache ( null )
234253 return
235254 }
236255
237256 if ( cache . value && ( cache . value . query !== q || cache . value . provider !== provider ) ) {
238- cache . value = null
257+ setCache ( null )
239258 await asyncData . refresh ( )
240259 return
241260 }
242261
243262 // Seed cache from asyncData for Algolia (which skips cache on initial fetch)
244263 if ( ! cache . value && asyncData . data . value ) {
245264 const { searchResponse } = asyncData . data . value
246- cache . value = {
247- query : q ,
248- provider,
249- objects : [ ...searchResponse . objects ] ,
250- total : searchResponse . total ,
251- }
265+ setCache ( [ ...searchResponse . objects ] , searchResponse . total )
252266 }
253267
254268 const currentCount = cache . value ?. objects . length ?? 0
@@ -270,25 +284,17 @@ export function useSearch(
270284 if ( cache . value && cache . value . query === q && cache . value . provider === provider ) {
271285 const existingNames = new Set ( cache . value . objects . map ( obj => obj . package . name ) )
272286 const newObjects = response . objects . filter ( obj => ! existingNames . has ( obj . package . name ) )
273- cache . value = {
274- query : q ,
275- provider,
276- objects : [ ...cache . value . objects , ...newObjects ] ,
277- total : response . total ,
278- }
287+
288+ setCache ( [ ...cache . value . objects , ...newObjects ] , response . total )
279289 } else {
280- cache . value = {
281- query : q ,
282- provider,
283- objects : response . objects ,
284- total : response . total ,
285- }
290+ setCache ( response . objects , response . total )
286291 }
287292
288293 if (
289294 cache . value &&
290295 cache . value . objects . length < targetSize &&
291- cache . value . objects . length < cache . value . total
296+ cache . value . objects . length < cache . value . total &&
297+ cache . value . objects . length < SEARCH_ENGINE_HITS_LIMIT [ provider ] // additional protection from infinite loop
292298 ) {
293299 await fetchMore ( targetSize )
294300 }
@@ -310,7 +316,7 @@ export function useSearch(
310316 watch (
311317 ( ) => toValue ( searchProvider ) ,
312318 async ( ) => {
313- cache . value = null
319+ setCache ( null )
314320 existenceCache . value = { }
315321 await asyncData . refresh ( )
316322 const targetSize = toValue ( options ) . size
@@ -326,6 +332,7 @@ export function useSearch(
326332 isStale : false ,
327333 objects : cache . value . objects ,
328334 total : cache . value . total ,
335+ totalUnlimited : cache . value . totalUnlimited ,
329336 time : new Date ( ) . toISOString ( ) ,
330337 }
331338 }
0 commit comments