@@ -9,7 +9,9 @@ const repoWithDetailsArray = [];
99const API_KEY = process . env . API_KEY ;
1010const API_URL = process . env . API_URL ;
1111const REPO_NAMES = process . env . REPO_NAMES ; //comma separated list of repo names within bcgov org
12-
12+ const BASE_DELAY = process . env . BASE_DELAY ? parseInt ( process . env . BASE_DELAY , 10 ) : 60000 ; // 1 minute base delay
13+ const MAX_DELAY = process . env . MAX_DELAY ? parseInt ( process . env . MAX_DELAY , 10 ) : 600000 ; // 10 minutes maximum delay
14+ const JITTER_FACTOR = Math . random ( ) * 0.3 ; // Random jitter between 0-30%
1315/**
1416 * Fetches the bcgovpubcode yaml for the specified repo and branch
1517 * @param repoName
@@ -140,20 +142,68 @@ async function bulkLoadPubCodes(yamlArrayAsJson) {
140142 * @param query
141143 * @returns {Promise<*|{}|number[]> }
142144 */
145+ /**
146+ * Sends a GraphQL query to the GitHub API and handles rate limiting with exponential backoff.
147+ *
148+ * @async
149+ * @function getGraphQlResponseOnQuery
150+ * @param {string } query - The GraphQL query to send to the GitHub API
151+ * @returns {Promise<Object> } The response data from the GraphQL API
152+ * @throws {Error } Throws an error if all retry attempts fail or if a non-rate-limit error occurs
153+ *
154+ * @description
155+ * This function sends a GraphQL query to GitHub's API and implements:
156+ * - Authentication using a token
157+ * - Automatic retry for rate limit errors (up to 5 attempts)
158+ * - Exponential backoff with jitter to respect rate limits
159+ * - Detailed logging of retry attempts
160+ * https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#about-secondary-rate-limits
161+ * Retry delays increase exponentially, with random jitter added:
162+ */
143163async function getGraphQlResponseOnQuery ( query ) {
144- const response = await axios ( {
145- method : "post" ,
146- url : "https://api.github.com/graphql" ,
147- headers : {
148- Authorization : `Bearer ${ token } ` ,
149- "Content-Type" : "application/json" ,
150- "Cache-Control" : "no-cache" ,
151- } ,
152- data : {
153- query,
154- } ,
155- } ) ;
156- return response . data ;
164+ const maxRetries = 5 ;
165+ let retries = 0 ;
166+
167+ while ( retries < maxRetries ) {
168+ try {
169+ const response = await axios ( {
170+ method : "post" ,
171+ url : "https://api.github.com/graphql" ,
172+ headers : {
173+ Authorization : `Bearer ${ token } ` ,
174+ "Content-Type" : "application/json" ,
175+ "Cache-Control" : "no-cache" ,
176+ } ,
177+ data : {
178+ query,
179+ } ,
180+ } ) ;
181+ return response . data ;
182+ } catch ( error ) {
183+ if ( error . response ?. status === 403 && error . response ?. data ?. message ?. includes ( "rate limit" ) ) {
184+ retries ++ ;
185+ if ( retries >= maxRetries ) throw error ;
186+
187+
188+
189+ // Calculate delay with exponential backoff and jitter
190+ const exponentialPart = BASE_DELAY * ( 2 ** retries ) ;
191+ const jitterFactor = Math . random ( ) * 0.3 ; // Random jitter between 0-30%
192+ const jitterAmount = exponentialPart * jitterFactor ;
193+ const delay = Math . min ( exponentialPart + jitterAmount , MAX_DELAY ) ;
194+ // Example delay values (in milliseconds) per retry:
195+ // Retry 1: ~60000ms (1 min) + up to 18000ms jitter = ~78s
196+ // Retry 2: ~120000ms (2 min) + up to 36000ms jitter = ~156s
197+ // Retry 3: ~240000ms (4 min) + up to 72000ms jitter = ~312s
198+ // Retry 4: ~480000ms (8 min) + up to 144000ms jitter = ~624s
199+ // Retry 5: ~600000ms (10 min, capped, jitter ignored) = 10 min
200+ console . log ( `Rate limit hit. Retrying in ${ Math . round ( delay / 1000 ) } seconds... (Attempt ${ retries } /${ maxRetries } )` ) ;
201+ await new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
202+ } else {
203+ throw error ;
204+ }
205+ }
206+ }
157207}
158208
159209/**
0 commit comments