[READ-ONLY] a fast, modern browser for the npm registry
at main 102 lines 3.4 kB view raw
1import type { NpmSearchResponse, NpmSearchResult, PackageMetaResponse } from '#shared/types' 2import { emptySearchResponse, metaToSearchResult } from './search-utils' 3import { mapWithConcurrency } from '#shared/utils/async' 4 5/** 6 * Fetch all packages for an npm organization. 7 * 8 * 1. Gets the authoritative package list from the npm registry (single request) 9 * 2. Fetches metadata from Algolia by exact name (single request) 10 * 3. Falls back to lightweight server-side package-meta lookups 11 */ 12export function useOrgPackages(orgName: MaybeRefOrGetter<string>) { 13 const route = useRoute() 14 const { searchProvider } = useSearchProvider() 15 const searchProviderValue = computed(() => { 16 const p = normalizeSearchParam(route.query.p) 17 if (p === 'npm' || searchProvider.value === 'npm') return 'npm' 18 return 'algolia' 19 }) 20 const { getPackagesByName } = useAlgoliaSearch() 21 22 const asyncData = useLazyAsyncData( 23 () => `org-packages:${searchProviderValue.value}:${toValue(orgName)}`, 24 async ({ ssrContext }, { signal }) => { 25 const org = toValue(orgName) 26 if (!org) { 27 return emptySearchResponse() 28 } 29 30 // Get the authoritative package list from the npm registry (single request) 31 let packageNames: string[] 32 try { 33 const { packages } = await $fetch<{ packages: string[]; count: number }>( 34 `/api/registry/org/${encodeURIComponent(org)}/packages`, 35 { signal }, 36 ) 37 packageNames = packages 38 } catch (err) { 39 // Check if this is a 404 (org not found) 40 if (err && typeof err === 'object' && 'statusCode' in err && err.statusCode === 404) { 41 const error = createError({ 42 statusCode: 404, 43 statusMessage: 'Organization not found', 44 message: `The organization "@${org}" does not exist on npm`, 45 }) 46 if (import.meta.server) { 47 ssrContext!.payload.error = error 48 } 49 throw error 50 } 51 // For other errors (network, etc.), return empty array to be safe 52 packageNames = [] 53 } 54 55 if (packageNames.length === 0) { 56 return emptySearchResponse() 57 } 58 59 // Fetch metadata + downloads from Algolia (single request via getObjects) 60 if (searchProviderValue.value === 'algolia') { 61 try { 62 const response = await getPackagesByName(packageNames) 63 if (response.objects.length > 0) { 64 return response 65 } 66 } catch { 67 // Fall through to npm registry path 68 } 69 } 70 71 // npm fallback: fetch lightweight metadata via server proxy 72 const metaResults = await mapWithConcurrency( 73 packageNames, 74 async name => { 75 try { 76 return await $fetch<PackageMetaResponse>( 77 `/api/registry/package-meta/${encodePackageName(name)}`, 78 { signal }, 79 ) 80 } catch { 81 return null 82 } 83 }, 84 10, 85 ) 86 87 const results: NpmSearchResult[] = metaResults 88 .filter((meta): meta is PackageMetaResponse => meta !== null) 89 .map(metaToSearchResult) 90 91 return { 92 isStale: false, 93 objects: results, 94 total: results.length, 95 time: new Date().toISOString(), 96 } satisfies NpmSearchResponse 97 }, 98 { default: emptySearchResponse }, 99 ) 100 101 return asyncData 102}