Skip to content

Commit d9408b5

Browse files
committed
Merge remote-tracking branch 'origin' into feature/provenance
2 parents 723ee08 + 3ff7798 commit d9408b5

7 files changed

Lines changed: 206 additions & 120 deletions

File tree

app/composables/useCachedFetch.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { CachedFetchResult } from '#shared/utils/fetch-cache-config'
2+
13
/**
24
* Type for the cachedFetch function attached to event context.
35
*/
@@ -9,14 +11,19 @@ export type CachedFetchFunction = <T = unknown>(
911
headers?: Record<string, string>
1012
},
1113
ttl?: number,
12-
) => Promise<T>
14+
) => Promise<CachedFetchResult<T>>
1315

1416
/**
1517
* Get the cachedFetch function from the current request context.
1618
*
1719
* IMPORTANT: This must be called in the composable setup context (outside of
1820
* useAsyncData handlers). The returned function can then be used inside handlers.
1921
*
22+
* The returned function returns a wrapper object with staleness metadata:
23+
* - `data`: The response data
24+
* - `isStale`: Whether the data came from stale cache
25+
* - `cachedAt`: Unix timestamp when cached, or null if fresh fetch
26+
*
2027
* @example
2128
* ```ts
2229
* export function usePackage(name: MaybeRefOrGetter<string>) {
@@ -25,15 +32,18 @@ export type CachedFetchFunction = <T = unknown>(
2532
*
2633
* return useLazyAsyncData(
2734
* () => `package:${toValue(name)}`,
28-
* // Use it inside the handler
29-
* () => cachedFetch<Packument>(`https://registry.npmjs.org/${toValue(name)}`)
35+
* // Use it inside the handler - destructure { data } or { data, isStale }
36+
* async () => {
37+
* const { data } = await cachedFetch<Packument>(`https://registry.npmjs.org/${toValue(name)}`)
38+
* return data
39+
* }
3040
* )
3141
* }
3242
* ```
3343
* @public
3444
*/
3545
export function useCachedFetch(): CachedFetchFunction {
36-
// On client, return a function that just uses $fetch
46+
// On client, return a function that just uses $fetch (no caching, not stale)
3747
if (import.meta.client) {
3848
return async <T = unknown>(
3949
url: string,
@@ -43,8 +53,9 @@ export function useCachedFetch(): CachedFetchFunction {
4353
headers?: Record<string, string>
4454
} = {},
4555
_ttl?: number,
46-
): Promise<T> => {
47-
return (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
56+
): Promise<CachedFetchResult<T>> => {
57+
const data = (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
58+
return { data, isStale: false, cachedAt: null }
4859
}
4960
}
5061

@@ -67,7 +78,8 @@ export function useCachedFetch(): CachedFetchFunction {
6778
headers?: Record<string, string>
6879
} = {},
6980
_ttl?: number,
70-
): Promise<T> => {
71-
return (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
81+
): Promise<CachedFetchResult<T>> => {
82+
const data = (await $fetch(url, options as Parameters<typeof $fetch>[1])) as T
83+
return { data, isStale: false, cachedAt: null }
7284
}
7385
}

app/composables/useNpmRegistry.ts

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,25 @@ export function usePackage(
183183
) {
184184
const cachedFetch = useCachedFetch()
185185

186-
return useLazyAsyncData(
186+
const asyncData = useLazyAsyncData(
187187
() => `package:${toValue(name)}:${toValue(requestedVersion) ?? ''}`,
188188
async () => {
189189
const encodedName = encodePackageName(toValue(name))
190-
const r = await cachedFetch<Packument>(`${NPM_REGISTRY}/${encodedName}`)
190+
const { data: r, isStale } = await cachedFetch<Packument>(`${NPM_REGISTRY}/${encodedName}`)
191191
const reqVer = toValue(requestedVersion)
192192
const pkg = transformPackument(r, reqVer)
193193
const resolvedVersion = getResolvedVersion(pkg, reqVer)
194-
return { ...pkg, resolvedVersion }
194+
return { ...pkg, resolvedVersion, isStale }
195195
},
196196
)
197+
198+
if (import.meta.client && asyncData.data.value?.isStale) {
199+
onMounted(() => {
200+
asyncData.refresh()
201+
})
202+
}
203+
204+
return asyncData
197205
}
198206

199207
function getResolvedVersion(pkg: SlimPackument, reqVer?: string | null): string | null {
@@ -223,15 +231,24 @@ export function usePackageDownloads(
223231
) {
224232
const cachedFetch = useCachedFetch()
225233

226-
return useLazyAsyncData(
234+
const asyncData = useLazyAsyncData(
227235
() => `downloads:${toValue(name)}:${toValue(period)}`,
228236
async () => {
229237
const encodedName = encodePackageName(toValue(name))
230-
return await cachedFetch<NpmDownloadCount>(
238+
const { data, isStale } = await cachedFetch<NpmDownloadCount>(
231239
`${NPM_API}/downloads/point/${toValue(period)}/${encodedName}`,
232240
)
241+
return { ...data, isStale }
233242
},
234243
)
244+
245+
if (import.meta.client && asyncData.data.value?.isStale) {
246+
onMounted(() => {
247+
asyncData.refresh()
248+
})
249+
}
250+
251+
return asyncData
235252
}
236253

237254
type NpmDownloadsRangeResponse = {
@@ -260,6 +277,7 @@ export async function fetchNpmDownloadsRange(
260277
const emptySearchResponse = {
261278
objects: [],
262279
total: 0,
280+
isStale: false,
263281
time: new Date().toISOString(),
264282
} satisfies NpmSearchResponse
265283

@@ -305,7 +323,7 @@ export function useNpmSearch(
305323
// Use requested size for initial fetch
306324
params.set('size', String(opts.size ?? 25))
307325

308-
const response = await cachedFetch<NpmSearchResponse>(
326+
const { data: response, isStale } = await cachedFetch<NpmSearchResponse>(
309327
`${NPM_REGISTRY}/-/v1/search?${params.toString()}`,
310328
{},
311329
60,
@@ -317,7 +335,7 @@ export function useNpmSearch(
317335
total: response.total,
318336
}
319337

320-
return response
338+
return { ...response, isStale }
321339
},
322340
{ default: () => lastSearch || emptySearchResponse },
323341
)
@@ -357,7 +375,7 @@ export function useNpmSearch(
357375
params.set('size', String(size))
358376
params.set('from', String(from))
359377

360-
const response = await cachedFetch<NpmSearchResponse>(
378+
const { data: response } = await cachedFetch<NpmSearchResponse>(
361379
`${NPM_REGISTRY}/-/v1/search?${params.toString()}`,
362380
{},
363381
60,
@@ -405,6 +423,7 @@ export function useNpmSearch(
405423
const data = computed<NpmSearchResponse | null>(() => {
406424
if (cache.value) {
407425
return {
426+
isStale: false,
408427
objects: cache.value.objects,
409428
total: cache.value.total,
410429
time: new Date().toISOString(),
@@ -413,6 +432,12 @@ export function useNpmSearch(
413432
return asyncData.data.value
414433
})
415434

435+
if (import.meta.client && asyncData.data.value?.isStale) {
436+
onMounted(() => {
437+
asyncData.refresh()
438+
})
439+
}
440+
416441
// Whether there are more results available on the server (incremental mode only)
417442
const hasMore = computed(() => {
418443
if (!cache.value) return true
@@ -482,7 +507,7 @@ function packumentToSearchResult(pkg: MinimalPackument, weeklyDownloads?: number
482507
export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
483508
const cachedFetch = useCachedFetch()
484509

485-
return useLazyAsyncData(
510+
const asyncData = useLazyAsyncData(
486511
() => `org-packages:${toValue(orgName)}`,
487512
async () => {
488513
const org = toValue(orgName)
@@ -493,7 +518,7 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
493518
// Get all package names in the org
494519
let packageNames: string[]
495520
try {
496-
const data = await cachedFetch<Record<string, string>>(
521+
const { data } = await cachedFetch<Record<string, string>>(
497522
`${NPM_REGISTRY}/-/org/${encodeURIComponent(org)}/package`,
498523
)
499524
packageNames = Object.keys(data)
@@ -526,7 +551,10 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
526551
batch.map(async name => {
527552
try {
528553
const encoded = encodePackageName(name)
529-
return await cachedFetch<MinimalPackument>(`${NPM_REGISTRY}/${encoded}`)
554+
const { data: pkg } = await cachedFetch<MinimalPackument>(
555+
`${NPM_REGISTRY}/${encoded}`,
556+
)
557+
return pkg
530558
} catch {
531559
return null
532560
}
@@ -551,13 +579,16 @@ export function useOrgPackages(orgName: MaybeRefOrGetter<string>) {
551579
)
552580

553581
return {
582+
isStale: false,
554583
objects: results,
555584
total: results.length,
556585
time: new Date().toISOString(),
557586
} satisfies NpmSearchResponse
558587
},
559588
{ default: () => emptySearchResponse },
560589
)
590+
591+
return asyncData
561592
}
562593

563594
// ============================================================================
@@ -665,9 +696,9 @@ async function checkDependencyOutdated(
665696
if (cached) {
666697
packument = await cached
667698
} else {
668-
const promise = cachedFetch<Packument>(
669-
`${NPM_REGISTRY}/${encodePackageName(packageName)}`,
670-
).catch(() => null)
699+
const promise = cachedFetch<Packument>(`${NPM_REGISTRY}/${encodePackageName(packageName)}`)
700+
.then(({ data }) => data)
701+
.catch(() => null)
671702
packumentCache.set(packageName, promise)
672703
packument = await promise
673704
}

app/composables/useRepoMeta.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,12 @@ const githubAdapter: ProviderAdapter = {
145145
// Using UNGH to avoid API limitations of the Github API
146146
let res: UnghRepoResponse | null = null
147147
try {
148-
res = await cachedFetch<UnghRepoResponse>(
148+
const { data } = await cachedFetch<UnghRepoResponse>(
149149
`https://ungh.cc/repos/${ref.owner}/${ref.repo}`,
150150
{ headers: { 'User-Agent': 'npmx' } },
151151
REPO_META_TTL,
152152
)
153+
res = data
153154
} catch {
154155
return null
155156
}
@@ -210,11 +211,12 @@ const gitlabAdapter: ProviderAdapter = {
210211
const projectPath = encodeURIComponent(`${ref.owner}/${ref.repo}`)
211212
let res: GitLabProjectResponse | null = null
212213
try {
213-
res = await cachedFetch<GitLabProjectResponse>(
214+
const { data } = await cachedFetch<GitLabProjectResponse>(
214215
`https://${baseHost}/api/v4/projects/${projectPath}`,
215216
{ headers: { 'User-Agent': 'npmx' } },
216217
REPO_META_TTL,
217218
)
219+
res = data
218220
} catch {
219221
return null
220222
}
@@ -265,11 +267,12 @@ const bitbucketAdapter: ProviderAdapter = {
265267
async fetchMeta(cachedFetch, ref, links) {
266268
let res: BitbucketRepoResponse | null = null
267269
try {
268-
res = await cachedFetch<BitbucketRepoResponse>(
270+
const { data } = await cachedFetch<BitbucketRepoResponse>(
269271
`https://api.bitbucket.org/2.0/repositories/${ref.owner}/${ref.repo}`,
270272
{ headers: { 'User-Agent': 'npmx' } },
271273
REPO_META_TTL,
272274
)
275+
res = data
273276
} catch {
274277
return null
275278
}
@@ -322,11 +325,12 @@ const codebergAdapter: ProviderAdapter = {
322325
async fetchMeta(cachedFetch, ref, links) {
323326
let res: GiteaRepoResponse | null = null
324327
try {
325-
res = await cachedFetch<GiteaRepoResponse>(
328+
const { data } = await cachedFetch<GiteaRepoResponse>(
326329
`https://codeberg.org/api/v1/repos/${ref.owner}/${ref.repo}`,
327330
{ headers: { 'User-Agent': 'npmx' } },
328331
REPO_META_TTL,
329332
)
333+
res = data
330334
} catch {
331335
return null
332336
}
@@ -379,11 +383,12 @@ const giteeAdapter: ProviderAdapter = {
379383
async fetchMeta(cachedFetch, ref, links) {
380384
let res: GiteeRepoResponse | null = null
381385
try {
382-
res = await cachedFetch<GiteeRepoResponse>(
386+
const { data } = await cachedFetch<GiteeRepoResponse>(
383387
`https://gitee.com/api/v5/repos/${ref.owner}/${ref.repo}`,
384388
{ headers: { 'User-Agent': 'npmx' } },
385389
REPO_META_TTL,
386390
)
391+
res = data
387392
} catch {
388393
return null
389394
}
@@ -469,11 +474,12 @@ const giteaAdapter: ProviderAdapter = {
469474
// so caching may not apply for self-hosted instances
470475
let res: GiteaRepoResponse | null = null
471476
try {
472-
res = await cachedFetch<GiteaRepoResponse>(
477+
const { data } = await cachedFetch<GiteaRepoResponse>(
473478
`https://${ref.host}/api/v1/repos/${ref.owner}/${ref.repo}`,
474479
{ headers: { 'User-Agent': 'npmx' } },
475480
REPO_META_TTL,
476481
)
482+
res = data
477483
} catch {
478484
return null
479485
}
@@ -577,7 +583,7 @@ const tangledAdapter: ProviderAdapter = {
577583
// Tangled doesn't have a public JSON API, but we can scrape the star count
578584
// from the HTML page (it's in the hx-post URL as countHint=N)
579585
try {
580-
const html = await cachedFetch<string>(
586+
const { data: html } = await cachedFetch<string>(
581587
`https://tangled.org/${ref.owner}/${ref.repo}`,
582588
{ headers: { 'User-Agent': 'npmx', 'Accept': 'text/html' } },
583589
REPO_META_TTL,
@@ -594,7 +600,7 @@ const tangledAdapter: ProviderAdapter = {
594600
if (atUriMatch) {
595601
try {
596602
//Get counts of records that reference this repo in the atmosphere using constellation
597-
const allLinks = await cachedFetch<ConstellationAllLinksResponse>(
603+
const { data: allLinks } = await cachedFetch<ConstellationAllLinksResponse>(
598604
`https://constellation.microcosm.blue/links/all?target=${atUri}`,
599605
{ headers: { 'User-Agent': 'npmx' } },
600606
REPO_META_TTL,
@@ -655,11 +661,12 @@ const radicleAdapter: ProviderAdapter = {
655661
async fetchMeta(cachedFetch, ref, links) {
656662
let res: RadicleProjectResponse | null = null
657663
try {
658-
res = await cachedFetch<RadicleProjectResponse>(
664+
const { data } = await cachedFetch<RadicleProjectResponse>(
659665
`https://seed.radicle.at/api/v1/projects/${ref.repo}`,
660666
{ headers: { 'User-Agent': 'npmx' } },
661667
REPO_META_TTL,
662668
)
669+
res = data
663670
} catch {
664671
return null
665672
}
@@ -720,11 +727,12 @@ const forgejoAdapter: ProviderAdapter = {
720727

721728
let res: GiteaRepoResponse | null = null
722729
try {
723-
res = await cachedFetch<GiteaRepoResponse>(
730+
const { data } = await cachedFetch<GiteaRepoResponse>(
724731
`https://${ref.host}/api/v1/repos/${ref.owner}/${ref.repo}`,
725732
{ headers: { 'User-Agent': 'npmx' } },
726733
REPO_META_TTL,
727734
)
735+
res = data
728736
} catch {
729737
return null
730738
}

i18n/locales/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@
339339
"size": "Size",
340340
"deps": "Deps",
341341
"updated": "Updated",
342-
"install": "Install",
342+
"get_started": "Get started",
343343
"readme": "Readme",
344344
"maintainers": "Maintainers",
345345
"keywords": "Keywords",

0 commit comments

Comments
 (0)