Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions app/components/PackageVulnerabilityTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,11 @@ const props = defineProps<{
version: string
}>()

const {
data: vulnTree,
status,
fetch: fetchVulnTree,
} = useDependencyAnalysis(
const { data: vulnTree, status } = useDependencyAnalysis(
() => props.packageName,
() => props.version,
)

onMounted(() => fetchVulnTree())

const isExpanded = shallowRef(false)
const showAllPackages = shallowRef(false)
const showAllVulnerabilities = shallowRef(false)
Expand Down
46 changes: 4 additions & 42 deletions app/composables/useDependencyAnalysis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type { VulnerabilityTreeResult } from '#shared/types/dependency-analysis'

/**
* Shared composable for dependency analysis data (vulnerabilities, deprecated packages).
* Fetches once and caches the result so multiple components can use it.
Expand All @@ -9,44 +7,8 @@ export function useDependencyAnalysis(
packageName: MaybeRefOrGetter<string>,
version: MaybeRefOrGetter<string>,
) {
// Build a stable key from the current values
const name = toValue(packageName)
const ver = toValue(version)
const key = `dep-analysis:v1:${name}@${ver}`

// Use useState for SSR-safe caching across components
const data = useState<VulnerabilityTreeResult | null>(key, () => null)
const status = useState<'idle' | 'pending' | 'success' | 'error'>(`${key}:status`, () => 'idle')
const error = useState<Error | null>(`${key}:error`, () => null)

async function fetch() {
const pkgName = toValue(packageName)
const pkgVersion = toValue(version)

if (!pkgName || !pkgVersion) return

// Already fetched or fetching
if (status.value === 'success' || status.value === 'pending') return

status.value = 'pending'
error.value = null

try {
const result = await $fetch<VulnerabilityTreeResult>(
`/api/registry/vulnerabilities/${encodePackageName(pkgName)}/v/${pkgVersion}`,
)
data.value = result
status.value = 'success'
} catch (e) {
error.value = e instanceof Error ? e : new Error('Failed to fetch dependency analysis')
status.value = 'error'
}
}

return {
data: readonly(data),
status: readonly(status),
error: readonly(error),
fetch,
}
return useFetch(
() =>
`/api/registry/vulnerabilities/${encodePackageName(toValue(packageName))}/v/${toValue(version)}`,
)
}
21 changes: 3 additions & 18 deletions app/pages/[...package].vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,24 +93,9 @@ const { copied: copiedPkgName, copy: copyPkgName } = useClipboard({

// Fetch dependency analysis (lazy, client-side)
// This is the same composable used by PackageVulnerabilityTree and PackageDeprecatedTree
const {
data: vulnTree,
status: vulnTreeStatus,
fetch: fetchVulnTree,
} = useDependencyAnalysis(packageName, () => displayVersion.value?.version ?? '')
onMounted(() => {
// Fetch vulnerability tree once displayVersion is available
if (displayVersion.value) {
fetchVulnTree()
}
})
watch(
() => displayVersion.value?.version,
() => {
if (displayVersion.value) {
fetchVulnTree()
}
},
const { data: vulnTree, status: vulnTreeStatus } = useDependencyAnalysis(
packageName,
() => displayVersion.value?.version ?? '',
)

// Keep latestVersion for comparison (to show "(latest)" badge)
Expand Down
Loading