Skip to content

Commit f399656

Browse files
feat(client): add support for endpoint-specific base URLs
1 parent 416e38b commit f399656

File tree

3 files changed

+51
-4
lines changed

3 files changed

+51
-4
lines changed

src/core.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ export class APIPromise<T> extends Promise<T> {
171171
export abstract class APIClient {
172172
baseURL: string;
173173
apiVersion: string;
174+
#baseURLOverridden: boolean;
174175
maxRetries: number;
175176
timeout: number;
176177
httpAgent: Agent | undefined;
@@ -181,20 +182,23 @@ export abstract class APIClient {
181182
constructor({
182183
baseURL,
183184
apiVersion,
185+
baseURLOverridden,
184186
maxRetries = 2,
185187
timeout = 60000, // 1 minute
186188
httpAgent,
187189
fetch: overriddenFetch,
188190
}: {
189191
baseURL: string;
190192
apiVersion: string;
193+
baseURLOverridden: boolean;
191194
maxRetries?: number | undefined;
192195
timeout: number | undefined;
193196
httpAgent: Agent | undefined;
194197
fetch: Fetch | undefined;
195198
}) {
196199
this.baseURL = baseURL;
197200
this.apiVersion = apiVersion;
201+
this.#baseURLOverridden = baseURLOverridden;
198202
this.maxRetries = validatePositiveInteger('maxRetries', maxRetries);
199203
this.timeout = validatePositiveInteger('timeout', timeout);
200204
this.httpAgent = httpAgent;
@@ -305,7 +309,7 @@ export abstract class APIClient {
305309
{ retryCount = 0 }: { retryCount?: number } = {},
306310
): { req: RequestInit; url: string; timeout: number } {
307311
const options = { ...inputOptions };
308-
const { method, path, query, headers: headers = {} } = options;
312+
const { method, path, query, defaultBaseURL, headers: headers = {} } = options;
309313

310314
const body =
311315
ArrayBuffer.isView(options.body) || (options.__binaryRequest && typeof options.body === 'string') ?
@@ -315,7 +319,7 @@ export abstract class APIClient {
315319
: null;
316320
const contentLength = this.calculateContentLength(body);
317321

318-
const url = this.buildURL(path!, query);
322+
const url = this.buildURL(path!, query, defaultBaseURL);
319323
if ('timeout' in options) validatePositiveInteger('timeout', options.timeout);
320324
options.timeout = options.timeout ?? this.timeout;
321325
const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url);
@@ -508,11 +512,12 @@ export abstract class APIClient {
508512
return new PagePromise<PageClass, Item>(this, request, Page);
509513
}
510514

511-
buildURL<Req>(path: string, query: Req | null | undefined): string {
515+
buildURL<Req>(path: string, query: Req | null | undefined, defaultBaseURL?: string | undefined): string {
516+
const baseURL = (!this.#baseURLOverridden && defaultBaseURL) || this.baseURL;
512517
const url =
513518
isAbsoluteURL(path) ?
514519
new URL(path)
515-
: new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
520+
: new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
516521

517522
const defaultQuery = this.defaultQuery();
518523
if (!isEmptyObj(defaultQuery)) {
@@ -801,6 +806,7 @@ export type RequestOptions<
801806
query?: Req | undefined;
802807
body?: Req | null | undefined;
803808
headers?: Headers | undefined;
809+
defaultBaseURL?: string | undefined;
804810

805811
maxRetries?: number;
806812
stream?: boolean | undefined;
@@ -824,6 +830,7 @@ const requestOptionsKeys: KeysEnum<RequestOptions> = {
824830
query: true,
825831
body: true,
826832
headers: true,
833+
defaultBaseURL: true,
827834

828835
maxRetries: true,
829836
stream: true,

src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ export class Cloudflare extends Core.APIClient {
246246
super({
247247
baseURL: options.baseURL!,
248248
apiVersion: options.apiVersion!,
249+
baseURLOverridden: baseURL ? baseURL !== 'https://api.cloudflare.com/client/v4' : false,
249250
timeout: options.timeout ?? 60000 /* 1 minute */,
250251
httpAgent: options.httpAgent,
251252
maxRetries: options.maxRetries,
@@ -356,6 +357,13 @@ export class Cloudflare extends Core.APIClient {
356357
pipelines: API.Pipelines = new API.Pipelines(this);
357358
schemaValidation: API.SchemaValidation = new API.SchemaValidation(this);
358359

360+
/**
361+
* Check whether the base URL is set to its default.
362+
*/
363+
#baseURLOverridden(): boolean {
364+
return this.baseURL !== 'https://api.cloudflare.com/client/v4';
365+
}
366+
359367
protected override defaultQuery(): Core.DefaultQuery | undefined {
360368
return this._options.defaultQuery;
361369
}

tests/index.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,38 @@ describe('instantiate client', () => {
214214
});
215215
expect(client.baseURL).toEqual('https://api.cloudflare.com/client/v4');
216216
});
217+
218+
test('in request options', () => {
219+
const client = new Cloudflare({
220+
apiKey: '144c9defac04969c7bfad8efaa8ea194',
221+
apiEmail: '[email protected]',
222+
});
223+
expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual(
224+
'http://localhost:5000/option/foo',
225+
);
226+
});
227+
228+
test('in request options overridden by client options', () => {
229+
const client = new Cloudflare({
230+
apiKey: '144c9defac04969c7bfad8efaa8ea194',
231+
apiEmail: '[email protected]',
232+
baseURL: 'http://localhost:5000/client',
233+
});
234+
expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual(
235+
'http://localhost:5000/client/foo',
236+
);
237+
});
238+
239+
test('in request options overridden by env variable', () => {
240+
process.env['CLOUDFLARE_BASE_URL'] = 'http://localhost:5000/env';
241+
const client = new Cloudflare({
242+
apiKey: '144c9defac04969c7bfad8efaa8ea194',
243+
apiEmail: '[email protected]',
244+
});
245+
expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual(
246+
'http://localhost:5000/env/foo',
247+
);
248+
});
217249
});
218250

219251
test('maxRetries option is correctly set', () => {

0 commit comments

Comments
 (0)