@@ -115,6 +115,50 @@ export class GitHubCache {
115
115
return `repo:${ owner } /${ repo } :${ type } ${ strArgs } ` ;
116
116
}
117
117
118
+ /**
119
+ * An abstraction over general processing that:
120
+ * 1. tries getting stuff from Redis cache
121
+ * 2. calls the promise to get new data if no value is found in cache
122
+ * 3. store this new value back in the cache with an optional TTL before returning the value.
123
+ *
124
+ * @private
125
+ */
126
+ #processCached< RType extends Parameters < InstanceType < typeof Redis > [ "json" ] [ "set" ] > [ 2 ] > ( ) {
127
+ /**
128
+ * Inner currying function to circumvent unsupported partial inference
129
+ *
130
+ * @param cacheKey the cache key to fetch Redis with
131
+ * @param promise the promise to call to get new data if the cache is empty
132
+ * @param transformer the function that transforms the return from the promise to the target return value
133
+ * @param ttl the optional TTL to use for the newly cached data
134
+ *
135
+ * @see {@link https://github.com/microsoft/TypeScript/issues/26242|Partial type inference discussion }
136
+ */
137
+ return async < PromiseType > (
138
+ cacheKey : string ,
139
+ promise : ( ) => Promise < PromiseType > ,
140
+ transformer : ( from : Awaited < PromiseType > ) => RType | Promise < RType > ,
141
+ ttl : number | undefined = undefined
142
+ ) : Promise < RType > => {
143
+ const cachedValue = await this . #redis. json . get < RType > ( cacheKey ) ;
144
+ if ( cachedValue ) {
145
+ console . log ( `Cache hit for ${ cacheKey } ` ) ;
146
+ return cachedValue ;
147
+ }
148
+
149
+ console . log ( `Cache miss for ${ cacheKey } ` ) ;
150
+
151
+ const newValue = await transformer ( await promise ( ) ) ;
152
+
153
+ await this . #redis. json . set ( cacheKey , "$" , newValue ) ;
154
+ if ( ttl !== undefined ) {
155
+ await this . #redis. expire ( cacheKey , ttl ) ;
156
+ }
157
+
158
+ return newValue ;
159
+ } ;
160
+ }
161
+
118
162
/**
119
163
* Get the item (issue or pr) with the given information.
120
164
* Return the appropriate value if the type is defined or
@@ -167,28 +211,21 @@ export class GitHubCache {
167
211
* @throws Error if the issue is not found
168
212
*/
169
213
async getIssueDetails ( owner : string , repo : string , id : number ) {
170
- const cacheKey = this . #getRepoKey( owner , repo , "issue" , id ) ;
171
-
172
- const cachedDetails = await this . #redis. json . get < IssueDetails > ( cacheKey ) ;
173
- if ( cachedDetails ) {
174
- console . log ( `Cache hit for issue details for ${ cacheKey } ` ) ;
175
- return cachedDetails ;
176
- }
177
-
178
- console . log ( `Cache miss for issue details for ${ cacheKey } , fetching from the GitHub API` ) ;
179
-
180
- const [ { data : info } , { data : comments } , linkedPrs ] = await Promise . all ( [
181
- this . #octokit. rest . issues . get ( { owner, repo, issue_number : id } ) ,
182
- this . #octokit. rest . issues . listComments ( { owner, repo, issue_number : id } ) ,
183
- this . #getLinkedPullRequests( owner , repo , id )
184
- ] ) ;
185
-
186
- const details : IssueDetails = { info, comments, linkedPrs } ;
187
-
188
- await this . #redis. json . set ( cacheKey , "$" , details ) ;
189
- await this . #redis. expire ( cacheKey , FULL_DETAILS_TTL ) ;
190
-
191
- return details ;
214
+ return await this . #processCached< IssueDetails > ( ) (
215
+ this . #getRepoKey( owner , repo , "issue" , id ) ,
216
+ ( ) =>
217
+ Promise . all ( [
218
+ this . #octokit. rest . issues . get ( { owner, repo, issue_number : id } ) ,
219
+ this . #octokit. rest . issues . listComments ( { owner, repo, issue_number : id } ) ,
220
+ this . #getLinkedPullRequests( owner , repo , id )
221
+ ] ) ,
222
+ ( [ { data : info } , { data : comments } , linkedPrs ] ) => ( {
223
+ info,
224
+ comments,
225
+ linkedPrs
226
+ } ) ,
227
+ FULL_DETAILS_TTL
228
+ ) ;
192
229
}
193
230
194
231
/**
@@ -201,32 +238,25 @@ export class GitHubCache {
201
238
* @throws Error if the PR is not found
202
239
*/
203
240
async getPullRequestDetails ( owner : string , repo : string , id : number ) {
204
- const cacheKey = this . #getRepoKey( owner , repo , "pr" , id ) ;
205
-
206
- const cachedDetails = await this . #redis. json . get < PullRequestDetails > ( cacheKey ) ;
207
- if ( cachedDetails ) {
208
- console . log ( `Cache hit for PR details for ${ cacheKey } ` ) ;
209
- return cachedDetails ;
210
- }
211
-
212
- console . log ( `Cache miss for PR details for ${ id } , fetching from the GitHub API` ) ;
213
-
214
- const [ { data : info } , { data : comments } , { data : commits } , { data : files } , linkedIssues ] =
215
- await Promise . all ( [
216
- this . #octokit. rest . pulls . get ( { owner, repo, pull_number : id } ) ,
217
- this . #octokit. rest . issues . listComments ( { owner, repo, issue_number : id } ) ,
218
- this . #octokit. rest . pulls . listCommits ( { owner, repo, pull_number : id } ) ,
219
- this . #octokit. rest . pulls . listFiles ( { owner, repo, pull_number : id } ) ,
220
- this . #getLinkedIssues( owner , repo , id )
221
- ] ) ;
222
-
223
- const details : PullRequestDetails = { info, comments, commits, files, linkedIssues } ;
224
-
225
- // Cache the result
226
- await this . #redis. json . set ( cacheKey , "$" , details ) ;
227
- await this . #redis. expire ( cacheKey , FULL_DETAILS_TTL ) ;
228
-
229
- return details ;
241
+ return await this . #processCached< PullRequestDetails > ( ) (
242
+ this . #getRepoKey( owner , repo , "pr" , id ) ,
243
+ ( ) =>
244
+ Promise . all ( [
245
+ this . #octokit. rest . pulls . get ( { owner, repo, pull_number : id } ) ,
246
+ this . #octokit. rest . issues . listComments ( { owner, repo, issue_number : id } ) ,
247
+ this . #octokit. rest . pulls . listCommits ( { owner, repo, pull_number : id } ) ,
248
+ this . #octokit. rest . pulls . listFiles ( { owner, repo, pull_number : id } ) ,
249
+ this . #getLinkedIssues( owner , repo , id )
250
+ ] ) ,
251
+ ( [ { data : info } , { data : comments } , { data : commits } , { data : files } , linkedIssues ] ) => ( {
252
+ info,
253
+ comments,
254
+ commits,
255
+ files,
256
+ linkedIssues
257
+ } ) ,
258
+ FULL_DETAILS_TTL
259
+ ) ;
230
260
}
231
261
232
262
/**
@@ -364,22 +394,12 @@ export class GitHubCache {
364
394
* @returns the releases, either cached or fetched
365
395
*/
366
396
async getReleases ( repository : Repository ) {
367
- const cacheKey = this . #getRepoKey( repository . owner , repository . repoName , "releases" ) ;
368
-
369
- const cachedReleases = await this . #redis. json . get < GitHubRelease [ ] > ( cacheKey ) ;
370
- if ( cachedReleases ) {
371
- console . log ( `Cache hit for releases for ${ cacheKey } ` ) ;
372
- return cachedReleases ;
373
- }
374
-
375
- console . log ( `Cache miss for releases for ${ cacheKey } , fetching from GitHub API` ) ;
376
-
377
- const releases = await this . #fetchReleases( repository ) ;
378
-
379
- await this . #redis. json . set ( cacheKey , "$" , releases ) ;
380
- await this . #redis. expire ( cacheKey , RELEASES_TTL ) ;
381
-
382
- return releases ;
397
+ return await this . #processCached< GitHubRelease [ ] > ( ) (
398
+ this . #getRepoKey( repository . owner , repository . repoName , "releases" ) ,
399
+ ( ) => this . #fetchReleases( repository ) ,
400
+ releases => releases ,
401
+ RELEASES_TTL
402
+ ) ;
383
403
}
384
404
385
405
/**
@@ -520,90 +540,51 @@ export class GitHubCache {
520
540
* @private
521
541
*/
522
542
async getDescriptions ( owner : string , repo : string ) {
523
- const cacheKey = this . #getRepoKey( owner , repo , "descriptions" ) ;
524
-
525
- const cachedDescriptions = await this . #redis. json . get < { [ key : string ] : string } > ( cacheKey ) ;
526
- if ( cachedDescriptions ) {
527
- console . log ( `Cache hit for descriptions for ${ cacheKey } ` ) ;
528
- return cachedDescriptions ;
529
- }
530
-
531
- console . log ( `Cache miss for releases for ${ cacheKey } , fetching from GitHub API` ) ;
532
-
533
- const { data : allFiles } = await this . #octokit. rest . git . getTree ( {
534
- owner,
535
- repo,
536
- tree_sha : "HEAD" ,
537
- recursive : "true"
538
- } ) ;
539
-
540
- const allPackageJson = allFiles . tree
541
- . map ( ( { path } ) => path )
542
- . filter ( path => path !== undefined )
543
- . filter (
544
- path =>
545
- ! path . includes ( "/test/" ) &&
546
- ! path . includes ( "/e2e-tests/" ) &&
547
- ( path === "package.json" || path . endsWith ( "/package.json" ) )
548
- ) ;
549
-
550
- const descriptions = new Map < string , string > ( ) ;
551
- for ( const path of allPackageJson ) {
552
- const { data : packageJson } = await this . #octokit. rest . repos . getContent ( {
553
- owner,
554
- repo,
555
- path
556
- } ) ;
557
-
558
- if ( ! ( "content" in packageJson ) ) continue ; // filter out empty or multiple results
559
- const { content, encoding, type } = packageJson ;
560
- if ( type !== "file" || ! content ) continue ; // filter out directories and empty files
561
- const packageFile =
562
- encoding === "base64" ? Buffer . from ( content , "base64" ) . toString ( ) : content ;
563
-
564
- try {
565
- const { description } = JSON . parse ( packageFile ) as { description : string } ;
566
- if ( description ) descriptions . set ( path , description ) ;
567
- } catch {
568
- // ignore
569
- }
570
- }
571
-
572
- await this . #redis. json . set ( cacheKey , "$" , Object . fromEntries ( descriptions ) ) ;
573
- await this . #redis. expire ( cacheKey , DESCRIPTIONS_TTL ) ;
574
-
575
- return Object . fromEntries ( descriptions ) ;
576
- }
577
-
578
- /**
579
- * Checks if releases are present in the cache for the
580
- * given GitHub info
581
- *
582
- * @param owner the owner of the GitHub repository to check the
583
- * existence in the cache for
584
- * @param repo the name of the GitHub repository to check the
585
- * existence in the cache for
586
- * @param type the kind of cache to target
587
- * @returns whether the repository is cached or not
588
- */
589
- async exists ( owner : string , repo : string , type : KeyType ) {
590
- const cacheKey = this . #getRepoKey( owner , repo , type ) ;
591
- const result = await this . #redis. exists ( cacheKey ) ;
592
- return result === 1 ;
593
- }
543
+ return await this . #processCached< { [ key : string ] : string } > ( ) (
544
+ this . #getRepoKey( owner , repo , "descriptions" ) ,
545
+ ( ) =>
546
+ this . #octokit. rest . git . getTree ( {
547
+ owner,
548
+ repo,
549
+ tree_sha : "HEAD" ,
550
+ recursive : "true"
551
+ } ) ,
552
+ async ( { data : allFiles } ) => {
553
+ const allPackageJson = allFiles . tree
554
+ . map ( ( { path } ) => path )
555
+ . filter ( path => path !== undefined )
556
+ . filter (
557
+ path =>
558
+ ! path . includes ( "/test/" ) &&
559
+ ! path . includes ( "/e2e-tests/" ) &&
560
+ ( path === "package.json" || path . endsWith ( "/package.json" ) )
561
+ ) ;
594
562
595
- /**
596
- * Delete a repository from the cache
597
- *
598
- * @param owner the owner of the GitHub repository to remove
599
- * from the cache
600
- * @param repo the name of the GitHub repository to remove
601
- * from the cache
602
- * @param type the kind of cache to target
603
- */
604
- async deleteEntry ( owner : string , repo : string , type : KeyType ) {
605
- const cacheKey = this . #getRepoKey( owner , repo , type ) ;
606
- await this . #redis. del ( cacheKey ) ;
563
+ const descriptions = new Map < string , string > ( ) ;
564
+ for ( const path of allPackageJson ) {
565
+ const { data : packageJson } = await this . #octokit. rest . repos . getContent ( {
566
+ owner,
567
+ repo,
568
+ path
569
+ } ) ;
570
+
571
+ if ( ! ( "content" in packageJson ) ) continue ; // filter out empty or multiple results
572
+ const { content, encoding, type } = packageJson ;
573
+ if ( type !== "file" || ! content ) continue ; // filter out directories and empty files
574
+ const packageFile =
575
+ encoding === "base64" ? Buffer . from ( content , "base64" ) . toString ( ) : content ;
576
+
577
+ try {
578
+ const { description } = JSON . parse ( packageFile ) as { description : string } ;
579
+ if ( description ) descriptions . set ( path , description ) ;
580
+ } catch {
581
+ // ignore
582
+ }
583
+ }
584
+ return Object . fromEntries ( descriptions ) ;
585
+ } ,
586
+ DESCRIPTIONS_TTL
587
+ ) ;
607
588
}
608
589
}
609
590
0 commit comments