Skip to content

Commit 14e651c

Browse files
committed
fix: separate server handlers
The inner data was ending up with cached params somehow so this seems to solve that by caching the underlying full data set by itself.
1 parent de53377 commit 14e651c

File tree

2 files changed

+85
-85
lines changed

2 files changed

+85
-85
lines changed
Lines changed: 32 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
1-
import { hasBuiltInTypes } from '~~/shared/utils/package-analysis'
1+
export type { TimelineVersion } from '~~/server/utils/timeline'
22

33
const DEFAULT_LIMIT = 25
44

5-
export interface TimelineVersion {
6-
version: string
7-
time: string
8-
license?: string
9-
type?: string
10-
hasTypes?: boolean
11-
hasTrustedPublisher?: boolean
12-
hasProvenance?: boolean
13-
tags: string[]
14-
}
15-
165
export interface TimelineResponse {
176
versions: TimelineVersion[]
187
total: number
@@ -28,76 +17,34 @@ export interface TimelineResponse {
2817
* - /api/registry/timeline/packageName?offset=0&limit=25
2918
* - /api/registry/timeline/@scope/packageName?offset=0&limit=25
3019
*/
31-
export default defineCachedEventHandler(
32-
async event => {
33-
const pkgParam = getRouterParam(event, 'pkg')
34-
if (!pkgParam) {
35-
throw createError({ statusCode: 404, message: 'Package name is required' })
36-
}
37-
38-
let packageName: string
39-
try {
40-
packageName = decodeURIComponent(pkgParam)
41-
} catch {
42-
throw createError({ statusCode: 400, message: 'Invalid package name encoding' })
43-
}
44-
45-
const query = getQuery(event)
46-
const offset = Math.max(0, Number(query.offset) || 0)
47-
const limit = Math.max(1, Math.min(100, Number(query.limit) || DEFAULT_LIMIT))
48-
49-
try {
50-
const packument = await fetchNpmPackage(packageName)
51-
52-
const tagsByVersion = new Map<string, string[]>()
53-
for (const [tag, ver] of Object.entries(packument['dist-tags'] ?? {})) {
54-
const list = tagsByVersion.get(ver)
55-
if (list) list.push(tag)
56-
else tagsByVersion.set(ver, [tag])
57-
}
58-
59-
// Build full sorted list
60-
const allVersions = Object.keys(packument.versions)
61-
.filter(v => packument.time[v])
62-
.map(v => {
63-
const version = packument.versions[v]!
64-
let license = version.license
65-
if (license && typeof license === 'object' && 'type' in license) {
66-
license = (license as { type: string }).type
67-
}
68-
69-
return {
70-
version: v,
71-
time: packument.time[v]!,
72-
license: typeof license === 'string' ? license : undefined,
73-
type: typeof version.type === 'string' ? version.type : undefined,
74-
hasTypes: hasBuiltInTypes(version) || undefined,
75-
hasTrustedPublisher: version._npmUser?.trustedPublisher ? true : undefined,
76-
hasProvenance: version.dist?.attestations ? true : undefined,
77-
tags: tagsByVersion.get(v) ?? [],
78-
}
79-
})
80-
.sort((a, b) => Date.parse(b.time) - Date.parse(a.time))
81-
82-
return {
83-
versions: allVersions.slice(offset, offset + limit),
84-
total: allVersions.length,
85-
} satisfies TimelineResponse
86-
} catch (error: unknown) {
87-
handleApiError(error, {
88-
statusCode: 502,
89-
message: `Failed to fetch timeline for ${packageName}`,
90-
})
91-
}
92-
},
93-
{
94-
maxAge: CACHE_MAX_AGE_FIVE_MINUTES,
95-
swr: true,
96-
getKey: event => {
97-
const query = getQuery(event)
98-
const offset = Math.max(0, Number(query.offset) || 0)
99-
const limit = Math.max(1, Math.min(100, Number(query.limit) || DEFAULT_LIMIT))
100-
return `timeline:v1:${getRouterParam(event, 'pkg')}:${offset}:${limit}`
101-
},
102-
},
103-
)
20+
export default defineEventHandler(async event => {
21+
const pkgParam = getRouterParam(event, 'pkg')
22+
if (!pkgParam) {
23+
throw createError({ statusCode: 404, message: 'Package name is required' })
24+
}
25+
26+
let packageName: string
27+
try {
28+
packageName = decodeURIComponent(pkgParam)
29+
} catch {
30+
throw createError({ statusCode: 400, message: 'Invalid package name encoding' })
31+
}
32+
33+
const query = getQuery(event)
34+
const offset = Math.max(0, Number(query.offset) || 0)
35+
const limit = Math.max(1, Math.min(100, Number(query.limit) || DEFAULT_LIMIT))
36+
37+
try {
38+
const allVersions = await fetchTimeline(packageName)
39+
40+
return {
41+
versions: allVersions.slice(offset, offset + limit),
42+
total: allVersions.length,
43+
} satisfies TimelineResponse
44+
} catch (error: unknown) {
45+
handleApiError(error, {
46+
statusCode: 502,
47+
message: `Failed to fetch timeline for ${packageName}`,
48+
})
49+
}
50+
})

server/utils/timeline.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { hasBuiltInTypes } from '~~/shared/utils/package-analysis'
2+
3+
export interface TimelineVersion {
4+
version: string
5+
time: string
6+
license?: string
7+
type?: string
8+
hasTypes?: boolean
9+
hasTrustedPublisher?: boolean
10+
hasProvenance?: boolean
11+
tags: string[]
12+
}
13+
14+
export const fetchTimeline = defineCachedFunction(
15+
async (packageName: string): Promise<TimelineVersion[]> => {
16+
const packument = await fetchNpmPackage(packageName)
17+
18+
const tagsByVersion = new Map<string, string[]>()
19+
for (const [tag, ver] of Object.entries(packument['dist-tags'] ?? {})) {
20+
const list = tagsByVersion.get(ver)
21+
if (list) list.push(tag)
22+
else tagsByVersion.set(ver, [tag])
23+
}
24+
25+
return Object.keys(packument.versions)
26+
.filter(v => packument.time[v])
27+
.map(v => {
28+
const version = packument.versions[v]!
29+
let license = version.license
30+
if (license && typeof license === 'object' && 'type' in license) {
31+
license = (license as { type: string }).type
32+
}
33+
34+
return {
35+
version: v,
36+
time: packument.time[v]!,
37+
license: typeof license === 'string' ? license : undefined,
38+
type: typeof version.type === 'string' ? version.type : undefined,
39+
hasTypes: hasBuiltInTypes(version) || undefined,
40+
hasTrustedPublisher: version._npmUser?.trustedPublisher ? true : undefined,
41+
hasProvenance: version.dist?.attestations ? true : undefined,
42+
tags: tagsByVersion.get(v) ?? [],
43+
}
44+
})
45+
.sort((a, b) => Date.parse(b.time) - Date.parse(a.time))
46+
},
47+
{
48+
maxAge: CACHE_MAX_AGE_FIVE_MINUTES,
49+
swr: true,
50+
name: 'timeline',
51+
getKey: (packageName: string) => `timeline:v1:${packageName}`,
52+
},
53+
)

0 commit comments

Comments
 (0)