@@ -12,8 +12,10 @@ import {
1212 timeIntervalToMilliseconds ,
1313} from '../../../private/node/conf-store.js'
1414import { LocalStorage } from '../local-storage.js'
15- import { abortSignalFromRequestBehaviour , requestMode , RequestModeInput } from '../http.js'
15+ import { abortSignalFromRequestBehaviour , RequestBehaviour , requestMode , RequestModeInput } from '../http.js'
1616import { CLI_KIT_VERSION } from '../../common/version.js'
17+ import { sleep } from '../system.js'
18+ import { outputContent , outputDebug } from '../output.js'
1719import {
1820 GraphQLClient ,
1921 rawRequest ,
@@ -65,6 +67,7 @@ type PerformGraphQLRequestOptions<TResult> = GraphQLRequestBaseOptions<TResult>
6567 queryAsString : string
6668 variables ?: Variables
6769 unauthorizedHandler ?: UnauthorizedHandler
70+ autoRateLimitRestore ?: boolean
6871}
6972
7073export type GraphQLRequestOptions < T > = GraphQLRequestBaseOptions < T > & {
@@ -77,13 +80,28 @@ export type GraphQLRequestDocOptions<TResult, TVariables> = GraphQLRequestBaseOp
7780 query : TypedDocumentNode < TResult , TVariables > | TypedDocumentNode < TResult , Exact < { [ key : string ] : never } > >
7881 variables ?: TVariables
7982 unauthorizedHandler ?: UnauthorizedHandler
83+ autoRateLimitRestore ?: boolean
84+ }
85+
86+ interface RunRawGraphQLRequestOptions < TResult > {
87+ client : {
88+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89+ setAbortSignal : ( signal : any ) => void
90+ rawRequest : ( query : string , variables ?: Variables ) => Promise < GraphQLResponse < TResult > >
91+ }
92+ behaviour : RequestBehaviour
93+ queryAsString : string
94+ variables ?: Variables
95+ autoRateLimitRestore : boolean
8096}
8197
8298export interface GraphQLResponseOptions < T > {
8399 handleErrors ?: boolean
84100 onResponse ?: ( response : GraphQLResponse < T > ) => void
85101}
86102
103+ const MAX_RATE_LIMIT_RESTORE_DELAY_SECONDS = 0.3
104+
87105async function createGraphQLClient ( {
88106 url,
89107 addedHeaders,
@@ -105,38 +123,74 @@ async function createGraphQLClient({
105123 }
106124}
107125
108- /**
109- * Handles execution of a GraphQL query.
110- *
111- * @param options - GraphQL request options.
112- */
126+ async function runSingleRawGraphQLRequest < TResult > (
127+ options : RunRawGraphQLRequestOptions < TResult > ,
128+ ) : Promise < GraphQLResponse < TResult > > {
129+ const { client, behaviour, queryAsString, variables, autoRateLimitRestore} = options
130+ let fullResponse : GraphQLResponse < TResult >
131+ // there is a errorPolicy option which returns rather than throwing on errors, but we _do_ ultimately want to
132+ // throw.
133+ try {
134+ client . setAbortSignal ( abortSignalFromRequestBehaviour ( behaviour ) )
135+ fullResponse = await client . rawRequest ( queryAsString , variables )
136+ await logLastRequestIdFromResponse ( fullResponse )
137+
138+ if ( autoRateLimitRestore ) {
139+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
140+ const cost : any = ( fullResponse . extensions as any ) ?. cost
141+ const actualQueryCost : unknown = cost ?. actualQueryCost
142+ const restoreRate : unknown = cost ?. throttleStatus ?. restoreRate
143+
144+ if ( actualQueryCost && typeof actualQueryCost === 'number' && restoreRate && typeof restoreRate === 'number' ) {
145+ const secondsToRestoreRate = actualQueryCost / restoreRate
146+ outputDebug ( outputContent `Sleeping for ${ secondsToRestoreRate . toString ( ) } seconds to restore the rate limit.` )
147+ await sleep ( Math . min ( secondsToRestoreRate , MAX_RATE_LIMIT_RESTORE_DELAY_SECONDS ) )
148+ }
149+ }
150+
151+ return fullResponse
152+ } catch ( error ) {
153+ if ( error instanceof ClientError ) {
154+ // error.response does have a headers property like a normal response, but it's not typed as such.
155+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156+ await logLastRequestIdFromResponse ( error . response as any )
157+ }
158+ throw error
159+ }
160+ }
161+
113162async function performGraphQLRequest < TResult > ( options : PerformGraphQLRequestOptions < TResult > ) {
114- const { token, addedHeaders, queryAsString, variables, api, url, responseOptions, unauthorizedHandler, cacheOptions} =
115- options
163+ const {
164+ token,
165+ addedHeaders,
166+ queryAsString,
167+ variables,
168+ api,
169+ url,
170+ responseOptions,
171+ unauthorizedHandler,
172+ cacheOptions,
173+ autoRateLimitRestore,
174+ } = options
116175 const behaviour = requestMode ( options . preferredBehaviour ?? 'default' )
117176
118177 let { headers, client} = await createGraphQLClient ( { url, addedHeaders, token} )
119178 debugLogRequestInfo ( api , queryAsString , url , variables , headers )
120179
121180 const rawGraphQLRequest = async ( ) => {
122- let fullResponse : GraphQLResponse < TResult >
123- // there is a errorPolicy option which returns rather than throwing on errors, but we _do_ ultimately want to
124- // throw.
125- try {
126- // mapping signal to any due to polyfill meaning types don't exactly match (but are functionally equivalent)
127- // eslint-disable-next-line @typescript-eslint/no-explicit-any
128- client . requestConfig . signal = abortSignalFromRequestBehaviour ( behaviour ) as any
129- fullResponse = await client . rawRequest < TResult > ( queryAsString , variables )
130- await logLastRequestIdFromResponse ( fullResponse )
131- return fullResponse
132- } catch ( error ) {
133- if ( error instanceof ClientError ) {
134- // error.response does have a headers property like a normal response, but it's not typed as such.
181+ return runSingleRawGraphQLRequest ( {
182+ client : {
135183 // eslint-disable-next-line @typescript-eslint/no-explicit-any
136- await logLastRequestIdFromResponse ( error . response as any )
137- }
138- throw error
139- }
184+ setAbortSignal : ( signal : any ) => {
185+ client . requestConfig . signal = signal
186+ } ,
187+ rawRequest : ( query : string , variables ?: Variables ) => client . rawRequest < TResult > ( query , variables ) ,
188+ } ,
189+ behaviour,
190+ queryAsString,
191+ variables,
192+ autoRateLimitRestore : autoRateLimitRestore ?? false ,
193+ } )
140194 }
141195
142196 const tokenRefreshHandler = unauthorizedHandler ?. handler
0 commit comments