@@ -2,10 +2,16 @@ import { format, sub } from "date-fns";
22import {
33 CranDownloadsResponse ,
44 CranResponse ,
5+ CranTopDownloadedPackagesRes ,
6+ CranTrendingPackagesRes ,
57 PackageDownloadTrend ,
68} from "./types" ;
9+ import { TopDownloadedPackagesRange } from "./package-insight.shape" ;
10+ import { slog } from "../modules/observability.server" ;
711
812export class PackageInsightService {
13+ private static readonly CRAN_LOGS_URL = "https://cranlogs.r-pkg.org" ;
14+
915 /**
1016 * Get the downloads for a package in the last n days, starting
1117 * from today. The result is an array of objects with the number
@@ -80,6 +86,24 @@ export class PackageInsightService {
8086 return downloads ;
8187 }
8288
89+ static async getTopDownloadedPackages (
90+ period : TopDownloadedPackagesRange ,
91+ count : number ,
92+ ) {
93+ return this . fetchFromCRAN < CranTopDownloadedPackagesRes > (
94+ `/top/${ period } /${ count . toString ( ) } ` ,
95+ ) ;
96+ }
97+
98+ static async getTrendingPackages ( ) {
99+ // Only for last week.
100+ const data = await this . fetchFromCRAN < CranTrendingPackagesRes > ( "/trending" ) ;
101+ return data . map ( ( item ) => ( {
102+ ...item ,
103+ increase : `${ new Number ( item . increase ) . toFixed ( 0 ) } %` ,
104+ } ) ) ;
105+ }
106+
83107 /*
84108 * Private.
85109 */
@@ -95,13 +119,26 @@ export class PackageInsightService {
95119 // the last day according to its point of reference (likely UTC).
96120 if ( days === 1 && ! from ) {
97121 return this
98- . fetchFromCRAN < CranDownloadsResponse > `/downloads/total/last-day/${ name } ` ;
122+ . fetchLogsFromCRAN < CranDownloadsResponse > `/downloads/total/last-day/${ name } ` ;
99123 }
100124
101125 const validFrom = from || new Date ( ) ;
102126 const past = sub ( validFrom , { days } ) ;
103127 return this
104- . fetchFromCRAN < CranDownloadsResponse > `/downloads/total/${ past } :${ validFrom } /${ name } ` ;
128+ . fetchLogsFromCRAN < CranDownloadsResponse > `/downloads/total/${ past } :${ validFrom } /${ name } ` ;
129+ }
130+
131+ private static async fetchFromCRAN < R extends CranResponse = CranResponse > (
132+ url : string ,
133+ ) : Promise < R > {
134+ return fetch ( this . CRAN_LOGS_URL + url , {
135+ headers : { "Content-Type" : "application/json" } ,
136+ } )
137+ . then ( ( response ) => response . json ( ) )
138+ . catch ( ( error ) => {
139+ slog . error ( "Failed to fetch CRAN statistics" , error ) ;
140+ return undefined ;
141+ } ) ;
105142 }
106143
107144 /**
@@ -112,18 +149,12 @@ export class PackageInsightService {
112149 * @param params
113150 * @returns
114151 */
115- private static async fetchFromCRAN < R extends CranResponse = CranResponse > (
152+ private static async fetchLogsFromCRAN < R extends CranResponse = CranResponse > (
116153 template : TemplateStringsArray ,
117154 ...params : ( string | Date ) [ ]
118155 ) : Promise < R > {
119156 const url = this . getCRANLogsUrl ( template , ...params ) ;
120- return fetch ( url , {
121- headers : {
122- "Content-Type" : "application/json" ,
123- } ,
124- } )
125- . then ( ( response ) => response . json ( ) )
126- . catch ( ( ) => undefined ) ;
157+ return this . fetchFromCRAN < R > ( url ) ;
127158 }
128159
129160 /**
@@ -152,7 +183,7 @@ export class PackageInsightService {
152183 return format ( part , "yyyy-MM-dd" ) ;
153184 } ) ;
154185
155- return "https://cranlogs.r-pkg.org" + stringified . join ( "" ) ;
186+ return stringified . join ( "" ) ;
156187 }
157188
158189 private static format1kDelimiter ( total : number ) {
0 commit comments