@@ -70,6 +70,10 @@ const FULL_DETAILS_TTL = 60 * 60 * 2; // 2 hours
7070 * The TTL of the cached descriptions, in seconds.
7171 */
7272const DESCRIPTIONS_TTL = 60 * 60 * 24 * 10 ; // 10 days
73+ /**
74+ * The TTL for non-deprecated packages, in seconds
75+ */
76+ const DEPRECATIONS_TTL = 60 * 60 * 24 * 2 ; // 2 days
7377
7478/**
7579 * A fetch layer to reach the GitHub API
@@ -115,6 +119,21 @@ export class GitHubCache {
115119 return `repo:${ owner } /${ repo } :${ type } ${ strArgs } ` ;
116120 }
117121
122+ /**
123+ * Generates a Redis key from the passed info.
124+ *
125+ * @param packageName the package name
126+ * @param args the optional additional values to append
127+ * at the end of the key; every element will be interpolated
128+ * in a string
129+ * @returns the pure computed key
130+ * @private
131+ */
132+ #getPackageKey( packageName : string , ...args : unknown [ ] ) {
133+ const strArgs = args . map ( a => `:${ a } ` ) ;
134+ return `package:${ packageName } ${ strArgs } ` ;
135+ }
136+
118137 /**
119138 * An abstraction over general processing that:
120139 * 1. tries getting stuff from Redis cache
@@ -138,7 +157,7 @@ export class GitHubCache {
138157 cacheKey : string ,
139158 promise : ( ) => Promise < PromiseType > ,
140159 transformer : ( from : Awaited < PromiseType > ) => RType | Promise < RType > ,
141- ttl : number | undefined = undefined
160+ ttl : number | ( ( value : RType ) => number | undefined ) | undefined = undefined
142161 ) : Promise < RType > => {
143162 const cachedValue = await this . #redis. json . get < RType > ( cacheKey ) ;
144163 if ( cachedValue ) {
@@ -152,7 +171,14 @@ export class GitHubCache {
152171
153172 await this . #redis. json . set ( cacheKey , "$" , newValue ) ;
154173 if ( ttl !== undefined ) {
155- await this . #redis. expire ( cacheKey , ttl ) ;
174+ if ( typeof ttl === "function" ) {
175+ const ttlResult = ttl ( newValue ) ;
176+ if ( ttlResult !== undefined ) {
177+ await this . #redis. expire ( cacheKey , ttlResult ) ;
178+ }
179+ } else {
180+ await this . #redis. expire ( cacheKey , ttl ) ;
181+ }
156182 }
157183
158184 return newValue ;
@@ -537,7 +563,6 @@ export class GitHubCache {
537563 * @param repo the GitHub repository name to fetch the
538564 * descriptions in
539565 * @returns a map of paths to descriptions.
540- * @private
541566 */
542567 async getDescriptions ( owner : string , repo : string ) {
543568 return await this . #processCached< { [ key : string ] : string } > ( ) (
@@ -586,6 +611,37 @@ export class GitHubCache {
586611 DESCRIPTIONS_TTL
587612 ) ;
588613 }
614+
615+ /**
616+ * Get the deprecation state of a package from its name.
617+ *
618+ * @param packageName the name of the package to search
619+ * @returns the deprecation status message if any, `false` otherwise
620+ */
621+ async getPackageDeprecation ( packageName : string ) {
622+ return await this . #processCached< { value : string | false } > ( ) (
623+ this . #getPackageKey( packageName , "deprecation" ) ,
624+ async ( ) => {
625+ try {
626+ // npmjs.org in a GitHub cache, I know, but hey, let's put that under the fact that
627+ // GitHub owns npmjs.org okay??
628+ const res = await fetch ( `https://registry.npmjs.org/${ packageName } /latest` ) ;
629+ if ( res . status !== 200 ) return { } ;
630+ return ( await res . json ( ) ) as { deprecated ?: boolean | string } ;
631+ } catch ( error ) {
632+ console . error ( `Error fetching npmjs.org for package ${ packageName } :` , error ) ;
633+ return { } ;
634+ }
635+ } ,
636+ ( { deprecated } ) => {
637+ if ( deprecated === undefined ) return { value : false } ;
638+ if ( typeof deprecated === "boolean" )
639+ return { value : deprecated && "This package is deprecated" } ;
640+ return { value : deprecated || "This package is deprecated" } ;
641+ } ,
642+ item => ( item . value === false ? DEPRECATIONS_TTL : undefined )
643+ ) ;
644+ }
589645}
590646
591647export const gitHubCache = new GitHubCache ( KV_REST_API_URL , KV_REST_API_TOKEN , GITHUB_TOKEN ) ;
0 commit comments