@@ -11,6 +11,7 @@ import type {OctokitOptions} from '@octokit/core';
1111import { Octokit } from '@octokit/rest' ;
1212import { RequestParameters } from '@octokit/types' ;
1313import { RequestError } from '@octokit/request-error' ;
14+ import { GraphqlResponseError } from '@octokit/graphql' ;
1415import { query } from 'typed-graphqlify' ;
1516import { Log } from '../logging' ;
1617
@@ -28,6 +29,52 @@ export interface GithubRepo {
2829 name : string ;
2930}
3031
32+ /** Helper to invoke an async function with retries. */
33+ async function invokeWithRetry < T > ( fn : ( ) => Promise < T > , retries = 3 , delay = 1000 ) : Promise < T > {
34+ let attempt = 0 ;
35+ while ( attempt < retries ) {
36+ try {
37+ return await fn ( ) ;
38+ } catch ( e ) {
39+ attempt ++ ;
40+ if ( attempt >= retries ) {
41+ throw e ;
42+ }
43+
44+ // Do not retry valid 4xx Client Errors (especially 404 Not Found)
45+ if ( isGithubApiError ( e ) && e . status < 500 ) {
46+ throw e ;
47+ }
48+
49+ // Do not retry GraphQL NOT_FOUND errors
50+ if ( e instanceof GraphqlResponseError && e . errors ?. every ( ( err ) => err . type === 'NOT_FOUND' ) ) {
51+ throw e ;
52+ }
53+
54+ Log . warn ( `GitHub API call failed (attempt ${ attempt } /${ retries } ). Retrying in ${ delay } ms...` ) ;
55+ await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ;
56+ }
57+ }
58+ throw new Error ( 'Unreachable' ) ;
59+ }
60+
61+ /** Creates a proxy that intercepts function calls and applies retries. */
62+ function createRetryProxy < T extends object > ( target : T ) : T {
63+ return new Proxy ( target , {
64+ get ( targetObj , prop , receiver ) {
65+ const value = Reflect . get ( targetObj , prop , receiver ) ;
66+ if ( typeof value === 'function' ) {
67+ return new Proxy ( value , {
68+ apply ( targetFn , thisArg , argArray ) {
69+ return invokeWithRetry ( ( ) => ( targetFn as Function ) . apply ( targetObj , argArray ) ) ;
70+ } ,
71+ } ) ;
72+ }
73+ return value ;
74+ } ,
75+ } ) ;
76+ }
77+
3178/** A Github client for interacting with the Github APIs. */
3279export class GithubClient {
3380 /** The octokit instance actually performing API requests. */
@@ -43,18 +90,18 @@ export class GithubClient {
4390 ...this . _octokitOptions ,
4491 } ) ;
4592
46- readonly pulls : Octokit [ 'pulls' ] = this . _octokit . pulls ;
47- readonly orgs : Octokit [ 'orgs' ] = this . _octokit . orgs ;
48- readonly repos : Octokit [ 'repos' ] = this . _octokit . repos ;
49- readonly issues : Octokit [ 'issues' ] = this . _octokit . issues ;
50- readonly git : Octokit [ 'git' ] = this . _octokit . git ;
51- readonly rateLimit : Octokit [ 'rateLimit' ] = this . _octokit . rateLimit ;
52- readonly teams : Octokit [ 'teams' ] = this . _octokit . teams ;
53- readonly search : Octokit [ 'search' ] = this . _octokit . search ;
54- readonly rest : Octokit [ 'rest' ] = this . _octokit . rest ;
93+ readonly pulls : Octokit [ 'pulls' ] = createRetryProxy ( this . _octokit . pulls ) ;
94+ readonly orgs : Octokit [ 'orgs' ] = createRetryProxy ( this . _octokit . orgs ) ;
95+ readonly repos : Octokit [ 'repos' ] = createRetryProxy ( this . _octokit . repos ) ;
96+ readonly issues : Octokit [ 'issues' ] = createRetryProxy ( this . _octokit . issues ) ;
97+ readonly git : Octokit [ 'git' ] = createRetryProxy ( this . _octokit . git ) ;
98+ readonly rateLimit : Octokit [ 'rateLimit' ] = createRetryProxy ( this . _octokit . rateLimit ) ;
99+ readonly teams : Octokit [ 'teams' ] = createRetryProxy ( this . _octokit . teams ) ;
100+ readonly search : Octokit [ 'search' ] = createRetryProxy ( this . _octokit . search ) ;
101+ readonly rest : Octokit [ 'rest' ] = createRetryProxy ( this . _octokit . rest ) ;
55102 readonly paginate : Octokit [ 'paginate' ] = this . _octokit . paginate ;
56- readonly checks : Octokit [ 'checks' ] = this . _octokit . checks ;
57- readonly users : Octokit [ 'users' ] = this . _octokit . users ;
103+ readonly checks : Octokit [ 'checks' ] = createRetryProxy ( this . _octokit . checks ) ;
104+ readonly users : Octokit [ 'users' ] = createRetryProxy ( this . _octokit . users ) ;
58105
59106 constructor ( private _octokitOptions ?: OctokitOptions ) { }
60107}
@@ -76,7 +123,9 @@ export class AuthenticatedGithubClient extends GithubClient {
76123
77124 /** Perform a query using Github's Graphql API. */
78125 async graphql < T extends GraphqlQueryObject > ( queryObject : T , params : RequestParameters = { } ) {
79- return ( await this . _graphql ( query ( queryObject ) . toString ( ) , params ) ) as T ;
126+ return invokeWithRetry ( async ( ) => {
127+ return ( await this . _graphql ( query ( queryObject ) . toString ( ) , params ) ) as T ;
128+ } ) ;
80129 }
81130}
82131
0 commit comments