| 65 | * |
| 66 | */ |
| 67 | export function usePackageComparison(packageNames: MaybeRefOrGetter<string[]>) { |
| 68 | const { t } = useI18n() |
| 69 | const { $npmRegistry } = useNuxtApp() |
| 70 | const numberFormatter = useNumberFormatter() |
| 71 | const compactNumberFormatter = useCompactNumberFormatter() |
| 72 | const bytesFormatter = useBytesFormatter() |
| 73 | const packages = computed(() => toValue(packageNames)) |
| 74 | |
| 75 | // Cache of fetched data by package name (source of truth) |
| 76 | const cache = shallowRef(new Map<string, PackageComparisonData>()) |
| 77 | |
| 78 | // Derived array in current package order |
| 79 | const packagesData = computed(() => packages.value.map(name => cache.value.get(name) ?? null)) |
| 80 | |
| 81 | const status = shallowRef<'idle' | 'pending' | 'success' | 'error'>('idle') |
| 82 | const error = shallowRef<Error | null>(null) |
| 83 | |
| 84 | // Track which packages are currently being fetched |
| 85 | const loadingPackages = shallowRef(new Set<string>()) |
| 86 | |
| 87 | // Track install size loading separately (it's slower) |
| 88 | const installSizeLoading = shallowRef(false) |
| 89 | |
| 90 | // Fetch function - only fetches packages not already in cache |
| 91 | async function fetchPackages(names: string[]) { |
| 92 | if (names.length === 0) { |
| 93 | status.value = 'idle' |
| 94 | return |
| 95 | } |
| 96 | |
| 97 | // Handle "no dependency" column - add to cache immediately |
| 98 | if (names.includes(NO_DEPENDENCY_ID) && !cache.value.has(NO_DEPENDENCY_ID)) { |
| 99 | const newCache = new Map(cache.value) |
| 100 | newCache.set(NO_DEPENDENCY_ID, createNoDependencyData()) |
| 101 | cache.value = newCache |
| 102 | } |
| 103 | |
| 104 | // Only fetch packages not already cached (excluding "no dep" which has no remote data) |
| 105 | const namesToFetch = names.filter(name => name !== NO_DEPENDENCY_ID && !cache.value.has(name)) |
| 106 | |
| 107 | if (namesToFetch.length === 0) { |
| 108 | status.value = 'success' |
| 109 | return |
| 110 | } |
| 111 | |
| 112 | status.value = 'pending' |
| 113 | error.value = null |
| 114 | |
| 115 | // Mark packages as loading |
| 116 | loadingPackages.value = new Set(namesToFetch) |
| 117 | |
| 118 | try { |
| 119 | // First pass: fetch fast data (package info, downloads, analysis, vulns) |
| 120 | const results = await Promise.all( |
| 121 | namesToFetch.map(async (name): Promise<PackageComparisonData | null> => { |
| 122 | try { |
| 123 | // Fetch basic package info first (required) |
| 124 | const { data: pkgData } = await $npmRegistry<Packument>(`/${encodePackageName(name)}`) |