1- import { hasBuiltInTypes } from '~~/shared /utils/package-analysis '
1+ export type { TimelineVersion } from '~~/server /utils/timeline '
22
33const 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-
165export 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+ } )
0 commit comments