@@ -14,11 +14,13 @@ import {
1414 MULTIPART_FORM ,
1515 URL_FORM ,
1616} from "./content-type" ;
17+ import { RetryStrategy , RetryConfig , sleep } from "./retry" ;
1718
1819export interface CoreClientProps {
1920 baseUrl : string | Record < string , string | undefined > ;
2021 timeout ?: number | undefined ;
2122 auths ?: Record < string , AuthProvider > ;
23+ retries ?: RetryStrategy ;
2224}
2325
2426export type ApiResponse = Response | NodeResponse ;
@@ -48,6 +50,7 @@ export interface RequestOptions {
4850 timeout ?: number ;
4951 additionalHeaders ?: Record < string , string > ;
5052 additionalQuery ?: Record < string , string > ;
53+ retries ?: RetryStrategy ;
5154}
5255
5356const _DEFAULT_SERVICE_NAME = "__default_service__" ;
@@ -56,6 +59,7 @@ export class CoreClient {
5659 private baseUrl : Record < string , string | undefined > ;
5760 private auths : Record < string , AuthProvider > ;
5861 private timeout : number | undefined ;
62+ private retries ?: RetryStrategy | undefined ;
5963
6064 constructor ( props : CoreClientProps ) {
6165 this . baseUrl =
@@ -64,6 +68,7 @@ export class CoreClient {
6468 : props . baseUrl ;
6569 this . auths = props . auths ?? { } ;
6670 this . timeout = props . timeout ;
71+ this . retries = props . retries ;
6772 }
6873
6974 private async applyAuths ( cfg : RequestConfig ) : Promise < RequestConfig > {
@@ -116,7 +121,7 @@ export class CoreClient {
116121
117122 private encodeBodyByContentType (
118123 cfg : RequestConfig ,
119- reqInit : RequestInit ,
124+ reqInit : RequestInit
120125 ) : RequestInit {
121126 const contentTypeOverride =
122127 cfg . opts ?. additionalHeaders ?. [ "content-type" ] ??
@@ -136,9 +141,8 @@ export class CoreClient {
136141
137142 if ( RUNTIME . type === "node" ) {
138143 // explicitly set boundary
139- headers [
140- "content-type"
141- ] = `${ MULTIPART_FORM } ; boundary=${ form . getBoundary ( ) } ` ;
144+ headers [ "content-type" ] =
145+ `${ MULTIPART_FORM } ; boundary=${ form . getBoundary ( ) } ` ;
142146 } else {
143147 // the browser should automatically set the content type
144148 delete headers [ "content-type" ] ;
@@ -149,7 +153,7 @@ export class CoreClient {
149153 } else if ( contentType === URL_FORM ) {
150154 if ( typeof cfg . body !== "object" ) {
151155 throw new TypeError (
152- "x-www-form-urlencoded data must be an object at the top level" ,
156+ "x-www-form-urlencoded data must be an object at the top level"
153157 ) ;
154158 }
155159
@@ -182,29 +186,56 @@ export class CoreClient {
182186 return reqInit ;
183187 }
184188
185- private async request ( cfg : RequestConfig ) : Promise < ApiResponse > {
186- const fetcherFn =
187- RUNTIME . type === "node" || typeof fetch !== "function"
188- ? nodeFetch
189- : fetch ;
190-
191- cfg = await this . applyAuths ( cfg ) ;
192- const reqInit = this . buildRequestInit ( cfg ) ;
193- const url = this . buildUrlFromCfg ( cfg ) ;
194-
195- const timeout = cfg . opts ?. timeout ?? this . timeout ;
189+ private async sendRequest ( {
190+ url,
191+ reqInit,
192+ timeout,
193+ } : {
194+ url : string ;
195+ reqInit : RequestInit ;
196+ timeout ?: number | undefined ;
197+ } ) : Promise < ApiResponse > {
196198 const controller = new AbortController ( ) ;
197199 let timeoutId ;
198200 if ( typeof timeout !== "undefined" ) {
199201 timeoutId = setTimeout ( ( ) => controller . abort ( ) , timeout ) ;
200202 }
201203 reqInit . signal = controller . signal ;
202204
205+ const fetcherFn =
206+ RUNTIME . type === "node" || typeof fetch !== "function"
207+ ? nodeFetch
208+ : fetch ;
203209 const response = await fetcherFn ( url , reqInit as any ) ;
204210
205211 if ( timeoutId ) {
206212 clearTimeout ( timeoutId ) ;
207213 }
214+ return response ;
215+ }
216+
217+ private async request ( cfg : RequestConfig ) : Promise < ApiResponse > {
218+ cfg = await this . applyAuths ( cfg ) ;
219+ const reqInit = this . buildRequestInit ( cfg ) ;
220+ const url = this . buildUrlFromCfg ( cfg ) ;
221+ const timeout = cfg . opts ?. timeout ?? this . timeout ;
222+ const sendRequestData = { url, reqInit, timeout } ;
223+
224+ let response = await this . sendRequest ( sendRequestData ) ;
225+ if ( cfg . opts ?. retries || this . retries ) {
226+ const retry = new RetryConfig ( {
227+ override : cfg . opts ?. retries ,
228+ base : this . retries ,
229+ } ) ;
230+ let attempt = 1 ;
231+ let delay = retry . initialDelay ;
232+ while ( retry . shouldRetry ( { attempt, statusCode : response . status } ) ) {
233+ await sleep ( delay ) ;
234+ response = await this . sendRequest ( sendRequestData ) ;
235+ delay = retry . calcNextDelay ( { currDelay : delay } ) ;
236+ attempt ++ ;
237+ }
238+ }
208239
209240 if ( ! response . ok ) {
210241 throw new ApiError ( cfg , response as any ) ;
0 commit comments