[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

perf(ui): abort claim modal check request on deduplication (#557)

authored by

Robin and committed by
GitHub
2317d7c4 31cb5042

+52 -31
+30 -23
app/components/ClaimPackageModal.vue
··· 1 1 <script setup lang="ts"> 2 - import type { CheckNameResult } from '~/utils/package-name' 3 2 import { checkPackageName } from '~/utils/package-name' 4 3 5 4 const props = defineProps<{ ··· 16 15 refreshState, 17 16 } = useConnector() 18 17 19 - // Fetch name availability when modal opens 20 - const checkResult = shallowRef<CheckNameResult | null>(null) 21 - 22 - const isChecking = shallowRef(false) 23 18 const isPublishing = shallowRef(false) 24 - const publishError = shallowRef<string | null>(null) 25 19 const publishSuccess = shallowRef(false) 20 + const publishError = shallowRef<string | null>(null) 26 21 27 - async function checkAvailability() { 28 - isChecking.value = true 29 - publishError.value = null 30 - try { 31 - checkResult.value = await checkPackageName(props.packageName) 32 - } catch (err) { 33 - publishError.value = err instanceof Error ? err.message : $t('claim.modal.failed_to_check') 34 - } finally { 35 - isChecking.value = false 36 - } 37 - } 22 + const { 23 + data: checkResult, 24 + refresh: checkAvailability, 25 + status, 26 + error: checkError, 27 + } = useAsyncData( 28 + (_nuxtApp, { signal }) => { 29 + return checkPackageName(props.packageName, { signal }) 30 + }, 31 + { default: () => null, immediate: false }, 32 + ) 33 + 34 + const isChecking = computed(() => { 35 + return status.value === 'pending' 36 + }) 37 + 38 + const mergedError = computed(() => { 39 + return ( 40 + publishError.value ?? 41 + (checkError.value instanceof Error 42 + ? checkError.value.message 43 + : $t('claim.modal.failed_to_check')) 44 + ) 45 + }) 38 46 39 47 const connectorModal = useModal('connector-modal') 40 48 ··· 92 100 93 101 function open() { 94 102 // Reset state and check availability each time modal is opened 95 - checkResult.value = null 96 103 publishError.value = null 97 104 publishSuccess.value = false 98 105 checkAvailability() ··· 287 294 288 295 <!-- Error message --> 289 296 <div 290 - v-if="publishError" 297 + v-if="mergedError" 291 298 role="alert" 292 299 class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md" 293 300 > 294 - {{ publishError }} 301 + {{ mergedError }} 295 302 </div> 296 303 297 304 <!-- Actions --> ··· 369 376 </div> 370 377 371 378 <!-- Error state --> 372 - <div v-else-if="publishError" class="space-y-4"> 379 + <div v-else-if="mergedError" class="space-y-4"> 373 380 <div 374 381 role="alert" 375 382 class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md" 376 383 > 377 - {{ publishError }} 384 + {{ mergedError }} 378 385 </div> 379 386 <button 380 387 type="button" 381 388 class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50" 382 - @click="checkAvailability" 389 + @click="() => checkAvailability()" 383 390 > 384 391 {{ $t('common.retry') }} 385 392 </button>
+6 -2
app/composables/useNpmRegistry.ts
··· 26 26 * Uses bulk API for unscoped packages, parallel individual requests for scoped. 27 27 * Note: npm bulk downloads API does not support scoped packages. 28 28 */ 29 - async function fetchBulkDownloads(packageNames: string[]): Promise<Map<string, number>> { 29 + async function fetchBulkDownloads( 30 + packageNames: string[], 31 + options: Parameters<typeof $fetch>[1] = {}, 32 + ): Promise<Map<string, number>> { 30 33 const downloads = new Map<string, number>() 31 34 if (packageNames.length === 0) return downloads 32 35 ··· 44 47 try { 45 48 const response = await $fetch<Record<string, { downloads: number } | null>>( 46 49 `${NPM_API}/downloads/point/last-week/${chunk.join(',')}`, 50 + options, 47 51 ) 48 52 for (const [name, data] of Object.entries(response)) { 49 53 if (data?.downloads !== undefined) { ··· 572 576 return results 573 577 })(), 574 578 // Fetch downloads in bulk 575 - fetchBulkDownloads(packageNames), 579 + fetchBulkDownloads(packageNames, { signal }), 576 580 ]) 577 581 578 582 // Convert to search results with download data
+16 -6
app/utils/package-name.ts
··· 72 72 73 73 const NPM_REGISTRY = 'https://registry.npmjs.org' 74 74 75 - export async function checkPackageExists(name: string): Promise<boolean> { 75 + export async function checkPackageExists( 76 + name: string, 77 + options: Parameters<typeof $fetch>[1] = {}, 78 + ): Promise<boolean> { 76 79 try { 77 80 const encodedName = name.startsWith('@') 78 81 ? `@${encodeURIComponent(name.slice(1))}` 79 82 : encodeURIComponent(name) 80 83 81 84 await $fetch(`${NPM_REGISTRY}/${encodedName}`, { 85 + ...options, 82 86 method: 'HEAD', 83 87 }) 84 88 return true ··· 87 91 } 88 92 } 89 93 90 - export async function findSimilarPackages(name: string): Promise<SimilarPackage[]> { 94 + export async function findSimilarPackages( 95 + name: string, 96 + options: Parameters<typeof $fetch>[1] = {}, 97 + ): Promise<SimilarPackage[]> { 91 98 const normalized = normalizePackageName(name) 92 99 const similar: SimilarPackage[] = [] 93 100 ··· 99 106 description?: string 100 107 } 101 108 }> 102 - }>(`${NPM_REGISTRY}/-/v1/search?text=${encodeURIComponent(name)}&size=20`) 109 + }>(`${NPM_REGISTRY}/-/v1/search?text=${encodeURIComponent(name)}&size=20`, options) 103 110 104 111 for (const obj of searchResponse.objects) { 105 112 const pkgName = obj.package.name ··· 153 160 } 154 161 } 155 162 156 - export async function checkPackageName(name: string): Promise<CheckNameResult> { 163 + export async function checkPackageName( 164 + name: string, 165 + options: Parameters<typeof $fetch>[1] = {}, 166 + ): Promise<CheckNameResult> { 157 167 const validation = validatePackageName(name) 158 168 const valid = validation.validForNewPackages === true 159 169 ··· 177 187 178 188 // Check if package exists and find similar packages in parallel 179 189 const [exists, similarPackages] = await Promise.all([ 180 - checkPackageExists(name), 181 - findSimilarPackages(name), 190 + checkPackageExists(name, options), 191 + findSimilarPackages(name, options), 182 192 ]) 183 193 184 194 result.available = !exists