create-request 1.4.2 → 1.4.3-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -85,7 +85,7 @@ exports.ReferrerPolicy = void 0;
85
85
  /**
86
86
  * Cache modes for fetch requests
87
87
  */
88
- var CacheMode;
88
+ exports.CacheMode = void 0;
89
89
  (function (CacheMode) {
90
90
  CacheMode["RELOAD"] = "reload";
91
91
  CacheMode["DEFAULT"] = "default";
@@ -93,7 +93,7 @@ var CacheMode;
93
93
  CacheMode["NO_STORE"] = "no-store";
94
94
  CacheMode["FORCE_CACHE"] = "force-cache";
95
95
  CacheMode["ONLY_IF_CACHED"] = "only-if-cached";
96
- })(CacheMode || (CacheMode = {}));
96
+ })(exports.CacheMode || (exports.CacheMode = {}));
97
97
 
98
98
  class RequestError extends Error {
99
99
  status;
@@ -103,7 +103,7 @@ class RequestError extends Error {
103
103
  isTimeout;
104
104
  isAborted;
105
105
  constructor(message, url, method, options = {}) {
106
- super(message);
106
+ super(message, { cause: options.cause });
107
107
  this.name = "RequestError";
108
108
  this.url = url;
109
109
  this.method = method;
@@ -122,7 +122,7 @@ class RequestError extends Error {
122
122
  * Static methods for creating specific types of RequestError
123
123
  */
124
124
  static timeout(url, method, timeoutMs) {
125
- return new RequestError(`Timeout: ${timeoutMs}ms`, url, method, {
125
+ return new RequestError(`Timeout ${timeoutMs}ms`, url, method, {
126
126
  isTimeout: true,
127
127
  });
128
128
  }
@@ -164,16 +164,16 @@ class RequestError extends Error {
164
164
  // Check for connection errors (but not timeout errors)
165
165
  const isConnectionError = !isTimeoutError && (errorCode === "ECONNREFUSED" || errorCode === "ECONNRESET" || stack.includes("ECONNREFUSED") || stack.includes("connect"));
166
166
  if (isTimeoutError) {
167
- message = `Network error: Request timeout for ${url}`;
167
+ message = `Timeout ${url}`;
168
168
  }
169
169
  else if (isDnsError) {
170
- message = `Network error: Unable to resolve hostname for ${url}`;
170
+ message = `DNS error ${url}`;
171
171
  }
172
172
  else if (isConnectionError) {
173
- message = `Network error: Connection refused for ${url}`;
173
+ message = `Connection refused ${url}`;
174
174
  }
175
175
  else {
176
- message = `Network error: Failed to fetch ${url}`;
176
+ message = `Network error ${url}`;
177
177
  }
178
178
  }
179
179
  const error = new RequestError(message, url, method, {
@@ -200,11 +200,15 @@ class RequestError extends Error {
200
200
  * Wrapper for HTTP responses with methods to transform the response data
201
201
  */
202
202
  class ResponseWrapper {
203
- response;
204
203
  url;
205
204
  method;
206
- // GraphQL-specific options
205
+ response;
207
206
  graphQLOptions;
207
+ // Cache the body as the last used method
208
+ cachedBlob;
209
+ cachedText;
210
+ cachedJson;
211
+ cachedArrayBuffer;
208
212
  constructor(response, url, method, graphQLOptions) {
209
213
  this.response = response;
210
214
  this.url = url;
@@ -231,6 +235,18 @@ class ResponseWrapper {
231
235
  get raw() {
232
236
  return this.response;
233
237
  }
238
+ /**
239
+ * Check if the response body has already been consumed and throw an error if so
240
+ * @throws RequestError if the body has already been consumed
241
+ */
242
+ checkBodyNotConsumed() {
243
+ if (this.response.bodyUsed) {
244
+ throw new RequestError("Body already consumed", this.url || "", this.method || "", {
245
+ status: this.response.status,
246
+ response: this.response,
247
+ });
248
+ }
249
+ }
234
250
  /**
235
251
  * Check for GraphQL errors and throw if throwOnError is enabled
236
252
  * @param data - The parsed JSON data
@@ -245,18 +261,17 @@ class ResponseWrapper {
245
261
  const errors = responseData.errors;
246
262
  const errorMessages = errors.map(x => typeof x === "string" ? x : x && typeof x === "object" && "message" in x ? String(x.message || "Unknown error") : String(x));
247
263
  const errorMessage = errorMessages.join(", ");
248
- throw new RequestError(`GraphQL request failed with errors: ${errorMessage}`, this.url || "", this.method || "", {
264
+ throw new RequestError(`GraphQL errors: ${errorMessage}`, this.url || "", this.method || "", {
249
265
  status: this.response.status,
250
266
  response: this.response,
251
267
  });
252
268
  }
253
269
  /**
254
270
  * Parse the response body as JSON
255
- * Note: This consumes the response body and can only be called once.
256
271
  * If GraphQL options are set with throwOnError=true, will check for GraphQL errors and throw.
257
272
  *
258
273
  * @returns The parsed JSON data
259
- * @throws {RequestError} When the request fails, JSON parsing fails, GraphQL errors occur (if throwOnError enabled), or body is already consumed
274
+ * @throws {RequestError} When the request fails, JSON parsing fails, or GraphQL errors occur (if throwOnError enabled).
260
275
  *
261
276
  * @example
262
277
  * const data = await response.getJson();
@@ -273,19 +288,16 @@ class ResponseWrapper {
273
288
  * }
274
289
  */
275
290
  async getJson() {
276
- if (this.response.bodyUsed) {
277
- throw new RequestError("Body already consumed", this.url || "", this.method || "", {
278
- status: this.response.status,
279
- response: this.response,
280
- });
281
- }
291
+ if (this.cachedJson !== undefined)
292
+ return this.cachedJson;
293
+ this.checkBodyNotConsumed();
282
294
  try {
283
295
  const parsed = await this.response.json();
296
+ this.cachedJson = parsed;
284
297
  this.checkGraphQLErrors(parsed);
285
298
  return parsed;
286
299
  }
287
300
  catch (error) {
288
- // Re-throw RequestErrors from checkGraphQLErrors without wrapping
289
301
  if (error instanceof RequestError) {
290
302
  throw error;
291
303
  }
@@ -298,23 +310,21 @@ class ResponseWrapper {
298
310
  }
299
311
  /**
300
312
  * Get the response body as text
301
- * Note: This consumes the response body and can only be called once.
302
313
  *
303
314
  * @returns The response text
304
- * @throws {RequestError} When reading fails or the response has already been consumed
315
+ * @throws {RequestError} When reading fails
305
316
  *
306
317
  * @example
307
318
  * const text = await response.getText();
308
319
  */
309
320
  async getText() {
310
- if (this.response.bodyUsed) {
311
- throw new RequestError("Body already consumed", this.url || "", this.method || "", {
312
- status: this.response.status,
313
- response: this.response,
314
- });
315
- }
321
+ if (this.cachedText !== undefined)
322
+ return this.cachedText;
323
+ this.checkBodyNotConsumed();
316
324
  try {
317
- return await this.response.text();
325
+ const text = await this.response.text();
326
+ this.cachedText = text;
327
+ return text;
318
328
  }
319
329
  catch (e) {
320
330
  const errorMessage = e instanceof Error ? e.message : String(e);
@@ -326,24 +336,22 @@ class ResponseWrapper {
326
336
  }
327
337
  /**
328
338
  * Get the response body as a Blob
329
- * Note: This consumes the response body and can only be called once.
330
339
  *
331
340
  * @returns The response as a Blob
332
- * @throws {RequestError} When reading fails or the response has already been consumed
341
+ * @throws {RequestError} When reading fails
333
342
  *
334
343
  * @example
335
344
  * const blob = await response.getBlob();
336
345
  * const url = URL.createObjectURL(blob);
337
346
  */
338
347
  async getBlob() {
339
- if (this.response.bodyUsed) {
340
- throw new RequestError("Body already consumed", this.url || "", this.method || "", {
341
- status: this.response.status,
342
- response: this.response,
343
- });
344
- }
348
+ if (this.cachedBlob !== undefined)
349
+ return this.cachedBlob;
350
+ this.checkBodyNotConsumed();
345
351
  try {
346
- return await this.response.blob();
352
+ const blob = await this.response.blob();
353
+ this.cachedBlob = blob;
354
+ return blob;
347
355
  }
348
356
  catch (e) {
349
357
  const errorMessage = e instanceof Error ? e.message : String(e);
@@ -355,24 +363,23 @@ class ResponseWrapper {
355
363
  }
356
364
  /**
357
365
  * Get the response body as an ArrayBuffer
358
- * Note: This consumes the response body and can only be called once.
359
366
  *
360
367
  * @returns The response as an ArrayBuffer
361
- * @throws {RequestError} When reading fails or the response has already been consumed
368
+ * @throws {RequestError} When reading fails
362
369
  *
363
370
  * @example
364
371
  * const buffer = await response.getArrayBuffer();
365
372
  * const uint8Array = new Uint8Array(buffer);
366
373
  */
367
374
  async getArrayBuffer() {
368
- if (this.response.bodyUsed) {
369
- throw new RequestError("Body already consumed", this.url || "", this.method || "", {
370
- status: this.response.status,
371
- response: this.response,
372
- });
375
+ if (this.cachedArrayBuffer !== undefined) {
376
+ return this.cachedArrayBuffer;
373
377
  }
378
+ this.checkBodyNotConsumed();
374
379
  try {
375
- return await this.response.arrayBuffer();
380
+ const arrayBuffer = await this.response.arrayBuffer();
381
+ this.cachedArrayBuffer = arrayBuffer;
382
+ return arrayBuffer;
376
383
  }
377
384
  catch (e) {
378
385
  const errorMessage = e instanceof Error ? e.message : String(e);
@@ -385,6 +392,7 @@ class ResponseWrapper {
385
392
  /**
386
393
  * Get the raw response body as a ReadableStream
387
394
  * Note: This consumes the response body and should only be called once.
395
+ * Unlike other methods, streams cannot be cached, so this will throw if the body is already consumed.
388
396
  *
389
397
  * @returns The response body as a ReadableStream or null
390
398
  * @throws {RequestError} When the response body has already been consumed
@@ -397,12 +405,7 @@ class ResponseWrapper {
397
405
  * }
398
406
  */
399
407
  getBody() {
400
- if (this.response.bodyUsed) {
401
- throw new RequestError("Body already consumed", this.url || "", this.method || "", {
402
- status: this.response.status,
403
- response: this.response,
404
- });
405
- }
408
+ this.checkBodyNotConsumed();
406
409
  return this.response.body;
407
410
  }
408
411
  /**
@@ -894,17 +897,17 @@ class BaseRequest {
894
897
  }
895
898
  validateUrl(url) {
896
899
  if (!url?.trim())
897
- throw new RequestError("URL cannot be empty", url, this.method);
900
+ throw new RequestError("Invalid URL", url, this.method);
898
901
  if (url.includes("\0") || url.includes("\r") || url.includes("\n")) {
899
- throw new RequestError("Invalid URL (control chars)", url, this.method);
902
+ throw new RequestError("Invalid URL", url, this.method);
900
903
  }
901
904
  const trimmed = url.trim();
902
- if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
905
+ if (/^https?:\/\//.test(trimmed)) {
903
906
  try {
904
907
  new URL(trimmed);
905
908
  }
906
909
  catch {
907
- throw new RequestError(`Invalid URL: ${trimmed}`, trimmed, this.method);
910
+ throw new RequestError("Invalid URL", trimmed, this.method);
908
911
  }
909
912
  }
910
913
  }
@@ -945,11 +948,7 @@ class BaseRequest {
945
948
  * request.withHeader('Accept', 'application/json');
946
949
  */
947
950
  withHeader(key, value) {
948
- // Ignore null and undefined values
949
- if (value !== null && value !== undefined) {
950
- return this.withHeaders({ [key]: value });
951
- }
952
- return this;
951
+ return this.withHeaders({ [key]: value });
953
952
  }
954
953
  /**
955
954
  * Set a timeout for the request
@@ -964,7 +963,7 @@ class BaseRequest {
964
963
  */
965
964
  withTimeout(timeout) {
966
965
  if (!Number.isFinite(timeout) || timeout <= 0)
967
- throw new RequestError("Timeout must be a positive number", this.url, this.method);
966
+ throw new RequestError("Invalid timeout", this.url, this.method);
968
967
  this.requestOptions.timeout = timeout;
969
968
  return this;
970
969
  }
@@ -1348,13 +1347,7 @@ class BaseRequest {
1348
1347
  * @returns The instance for chaining
1349
1348
  */
1350
1349
  withQueryParam(key, value) {
1351
- if (value === null || value === undefined)
1352
- return this;
1353
- if (Array.isArray(value))
1354
- value.forEach(v => this.queryParams.append(key, String(v)));
1355
- else
1356
- this.queryParams.append(key, String(value));
1357
- return this;
1350
+ return this.withQueryParams({ [key]: value });
1358
1351
  }
1359
1352
  /**
1360
1353
  * Sets the request mode, which determines the CORS (Cross-Origin Resource Sharing) behavior
@@ -1398,9 +1391,7 @@ class BaseRequest {
1398
1391
  * @returns The instance for chaining
1399
1392
  */
1400
1393
  withContentType(contentType) {
1401
- return this.withHeaders({
1402
- "Content-Type": contentType,
1403
- });
1394
+ return this.withHeader("Content-Type", contentType);
1404
1395
  }
1405
1396
  /**
1406
1397
  * Shorthand for setting the Authorization header
@@ -1408,9 +1399,7 @@ class BaseRequest {
1408
1399
  * @returns The instance for chaining
1409
1400
  */
1410
1401
  withAuthorization(authValue) {
1411
- return this.withHeaders({
1412
- Authorization: authValue,
1413
- });
1402
+ return this.withHeader("Authorization", authValue);
1414
1403
  }
1415
1404
  /**
1416
1405
  * Shorthand for setting up Basic authentication
@@ -1440,7 +1429,7 @@ class BaseRequest {
1440
1429
  if (typeof Buffer !== "undefined")
1441
1430
  return Buffer.from(str).toString("base64");
1442
1431
  // Fallback (should never happen in modern environments)
1443
- throw new RequestError("Base64 encoding is not supported", this.url, this.method);
1432
+ throw new RequestError("Encoding not supported", this.url, this.method);
1444
1433
  }
1445
1434
  /**
1446
1435
  * Sets the bearer token for authentication
@@ -1525,9 +1514,7 @@ class BaseRequest {
1525
1514
  * @returns The instance for chaining
1526
1515
  */
1527
1516
  withCsrfToken(token, headerName = "X-CSRF-Token") {
1528
- return this.withHeaders({
1529
- [headerName]: token,
1530
- });
1517
+ return this.withHeader(headerName, token);
1531
1518
  }
1532
1519
  /**
1533
1520
  * Disables automatic anti-CSRF protection.
@@ -1543,9 +1530,7 @@ class BaseRequest {
1543
1530
  * @returns The instance for chaining
1544
1531
  */
1545
1532
  withAntiCsrfHeaders() {
1546
- return this.withHeaders({
1547
- "X-Requested-With": "XMLHttpRequest",
1548
- });
1533
+ return this.withHeader("X-Requested-With", "XMLHttpRequest");
1549
1534
  }
1550
1535
  /**
1551
1536
  * Add a request interceptor for this specific request
@@ -1714,9 +1699,7 @@ class BaseRequest {
1714
1699
  const config = Config.getInstance();
1715
1700
  // Apply anti-CSRF headers if enabled
1716
1701
  if (config.isAntiCsrfEnabled()) {
1717
- this.withHeaders({
1718
- "X-Requested-With": "XMLHttpRequest",
1719
- });
1702
+ this.withAntiCsrfHeaders();
1720
1703
  }
1721
1704
  // Apply global CSRF token if set
1722
1705
  const globalToken = config.getCsrfToken();
@@ -1724,9 +1707,7 @@ class BaseRequest {
1724
1707
  const csrfHeaderName = config.getCsrfHeaderName();
1725
1708
  const hasLocalToken = this.hasHeader("X-CSRF-Token") || this.hasHeader(csrfHeaderName);
1726
1709
  if (!hasLocalToken) {
1727
- this.withHeaders({
1728
- [csrfHeaderName]: globalToken,
1729
- });
1710
+ this.withHeader(csrfHeaderName, globalToken);
1730
1711
  }
1731
1712
  }
1732
1713
  // Check for XSRF token in cookies and send it as a header
@@ -1736,9 +1717,7 @@ class BaseRequest {
1736
1717
  const xsrfHeaderName = config.getXsrfHeaderName();
1737
1718
  const hasLocalToken = this.hasHeader("X-XSRF-TOKEN") || this.hasHeader(xsrfHeaderName);
1738
1719
  if (!hasLocalToken) {
1739
- this.withHeaders({
1740
- [xsrfHeaderName]: xsrfToken,
1741
- });
1720
+ this.withHeader(xsrfHeaderName, xsrfToken);
1742
1721
  }
1743
1722
  }
1744
1723
  }
@@ -1797,7 +1776,7 @@ class BaseRequest {
1797
1776
  const delay = typeof retriesConfig.delay === "function" ? retriesConfig.delay({ attempt: attempt + 1, error: requestError }) : retriesConfig.delay;
1798
1777
  // Validate delay result
1799
1778
  if (typeof delay !== "number" || !Number.isFinite(delay) || delay < 0) {
1800
- throw new RequestError(`Invalid retry delay: ${delay}`, url, method);
1779
+ throw new RequestError(`Invalid delay: ${delay}`, url, method);
1801
1780
  }
1802
1781
  // Wait for the delay
1803
1782
  if (delay > 0) {
@@ -1807,7 +1786,7 @@ class BaseRequest {
1807
1786
  }
1808
1787
  }
1809
1788
  // This should never happen but is needed for type safety
1810
- throw new RequestError(`Max retries reached`, url, method);
1789
+ throw new RequestError(`EO`, url, method);
1811
1790
  }
1812
1791
  /**
1813
1792
  * Run request interceptors in order: global interceptors first, then per-request
@@ -1829,7 +1808,7 @@ class BaseRequest {
1829
1808
  }
1830
1809
  catch (error) {
1831
1810
  const errorMessage = error instanceof Error ? error.message : String(error);
1832
- throw new RequestError(`Request interceptor failed: ${errorMessage}`, currentConfig.url, currentConfig.method);
1811
+ throw new RequestError(`Req Interceptor failed: ${errorMessage}`, currentConfig.url, currentConfig.method);
1833
1812
  }
1834
1813
  }
1835
1814
  return currentConfig;
@@ -1853,7 +1832,7 @@ class BaseRequest {
1853
1832
  const errorMessage = error instanceof Error ? error.message : String(error);
1854
1833
  const url = currentResponse.url || "";
1855
1834
  const method = currentResponse.method || "";
1856
- throw new RequestError(`Response interceptor failed: ${errorMessage}`, url, method);
1835
+ throw new RequestError(`Res Interceptor failed: ${errorMessage}`, url, method);
1857
1836
  }
1858
1837
  }
1859
1838
  return currentResponse;
@@ -1887,14 +1866,14 @@ class BaseRequest {
1887
1866
  const errorMessage = interceptorError instanceof Error ? interceptorError.message : String(interceptorError);
1888
1867
  // Always wrap in RequestError when we have context
1889
1868
  if (currentError instanceof RequestError) {
1890
- currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method, {
1869
+ currentError = new RequestError(`Err Interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method, {
1891
1870
  status: currentError.status,
1892
1871
  response: currentError.response,
1893
1872
  });
1894
1873
  }
1895
1874
  else if (currentError instanceof ResponseWrapper && currentError.url && currentError.method) {
1896
1875
  // If it's a ResponseWrapper, we can still create a proper RequestError from its context
1897
- currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method, {
1876
+ currentError = new RequestError(`Err Interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method, {
1898
1877
  status: currentError.status,
1899
1878
  });
1900
1879
  }
@@ -2137,7 +2116,7 @@ class BodyRequest extends BaseRequest {
2137
2116
  }
2138
2117
  catch (error) {
2139
2118
  const errorMessage = error instanceof Error ? error.message : String(error);
2140
- throw new RequestError(`JSON stringify failed: ${errorMessage}`, this.url, this.method);
2119
+ throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url, this.method);
2141
2120
  }
2142
2121
  }
2143
2122
  else {
@@ -2169,21 +2148,21 @@ class BodyRequest extends BaseRequest {
2169
2148
  */
2170
2149
  withGraphQL(query, variables, options) {
2171
2150
  if (typeof query !== "string" || query.length === 0) {
2172
- throw new RequestError("Invalid GraphQL query", this.url, this.method);
2151
+ throw new RequestError("Invalid query", this.url, this.method);
2173
2152
  }
2174
2153
  const graphQLBody = {
2175
2154
  query: query,
2176
2155
  };
2177
2156
  if (variables !== undefined) {
2178
2157
  if (typeof variables !== "object" || variables === null || Array.isArray(variables)) {
2179
- throw new RequestError("Invalid GraphQL variables", this.url, this.method);
2158
+ throw new RequestError("Invalid vars", this.url, this.method);
2180
2159
  }
2181
2160
  graphQLBody.variables = variables;
2182
2161
  }
2183
2162
  // Store GraphQL options if provided
2184
2163
  if (options !== undefined) {
2185
2164
  if (typeof options !== "object" || options === null || Array.isArray(options)) {
2186
- throw new RequestError("Invalid GraphQL options", this.url, this.method);
2165
+ throw new RequestError("Invalid options", this.url, this.method);
2187
2166
  }
2188
2167
  // Store only the known GraphQL options properties
2189
2168
  const opts = options;
@@ -2197,7 +2176,7 @@ class BodyRequest extends BaseRequest {
2197
2176
  }
2198
2177
  catch (error) {
2199
2178
  const errorMessage = error instanceof Error ? error.message : String(error);
2200
- throw new RequestError(`JSON stringify failed: ${errorMessage}`, this.url, this.method);
2179
+ throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url, this.method);
2201
2180
  }
2202
2181
  this.body = graphQLBody;
2203
2182
  this.bodyType = BodyType.JSON;
@@ -2456,15 +2435,280 @@ function options(url) {
2456
2435
  return new OptionsRequest(url);
2457
2436
  }
2458
2437
 
2438
+ /**
2439
+ * Resolves a URL against a base URL.
2440
+ * If the URL is absolute (starts with http:// or https://), it is returned as-is.
2441
+ * Otherwise, it is resolved relative to the base URL.
2442
+ *
2443
+ * @param baseURL - The base URL to resolve against, or undefined if not set
2444
+ * @param url - The URL to resolve, or undefined to return the base URL
2445
+ * @returns The resolved absolute URL string
2446
+ * @example
2447
+ * resolveURL("https://api.example.com", "/users") // "https://api.example.com/users"
2448
+ * resolveURL("https://api.example.com", "users") // "https://api.example.com/users"
2449
+ * resolveURL("https://api.example.com", "https://other.com") // "https://other.com"
2450
+ */
2451
+ function resolveURL(baseURL, url) {
2452
+ if (!url)
2453
+ return baseURL || "";
2454
+ if (!baseURL)
2455
+ return url;
2456
+ if (/^https?:\/\//.test(url))
2457
+ return url;
2458
+ try {
2459
+ return new URL(url, baseURL.endsWith("/") ? baseURL : baseURL + "/").toString();
2460
+ }
2461
+ catch {
2462
+ return baseURL.replace(/\/$/, "") + (url.startsWith("/") ? url : "/" + url);
2463
+ }
2464
+ }
2465
+ /**
2466
+ * API builder for creating configured API instances with default settings.
2467
+ * Allows you to set default headers, timeouts, authentication, and other options
2468
+ * that will be applied to all requests created through this builder.
2469
+ *
2470
+ * @example
2471
+ * ```typescript
2472
+ * const api = create.api()
2473
+ * .withBaseURL("https://api.example.com")
2474
+ * .withBearerToken("token123")
2475
+ * .withTimeout(5000);
2476
+ *
2477
+ * // All requests will use the base URL, bearer token, and timeout
2478
+ * await api.get("/users").getJson();
2479
+ * await api.post("/posts").withBody({ title: "Hello" }).getJson();
2480
+ * ```
2481
+ */
2482
+ class ApiBuilder {
2483
+ baseURL;
2484
+ modifiers;
2485
+ /**
2486
+ * Sets the base URL for all requests created through this API builder.
2487
+ * Relative URLs will be resolved against this base URL.
2488
+ *
2489
+ * @param baseURL - The base URL to use for all requests
2490
+ * @returns The API builder instance for method chaining
2491
+ * @example
2492
+ * ```typescript
2493
+ * const api = create.api().withBaseURL("https://api.example.com");
2494
+ * await api.get("/users").getJson(); // Requests https://api.example.com/users
2495
+ * ```
2496
+ */
2497
+ withBaseURL(baseURL) {
2498
+ this.baseURL = baseURL;
2499
+ return this;
2500
+ }
2501
+ /**
2502
+ * Adds a modifier function that will be applied to all requests created through this builder.
2503
+ *
2504
+ * @private
2505
+ * @param modifier - A function that modifies a request and returns it
2506
+ * @returns The API builder instance for method chaining
2507
+ */
2508
+ addModifier(modifier) {
2509
+ if (!this.modifiers)
2510
+ this.modifiers = [];
2511
+ this.modifiers.push(modifier);
2512
+ return this;
2513
+ }
2514
+ /**
2515
+ * Creates a GET request with the configured default settings.
2516
+ *
2517
+ * @param url - Optional URL path. If not provided, uses the base URL. If relative, resolves against base URL.
2518
+ * @returns A GetRequest instance ready to be executed
2519
+ * @example
2520
+ * ```typescript
2521
+ * const api = create.api().withBaseURL("https://api.example.com");
2522
+ * await api.get("/users").getJson();
2523
+ * await api.get("https://other.com/data").getJson(); // Absolute URL overrides base
2524
+ * ```
2525
+ */
2526
+ get = (url) => {
2527
+ const request = get(resolveURL(this.baseURL, url));
2528
+ if (this.modifiers)
2529
+ for (const modifier of this.modifiers)
2530
+ modifier(request);
2531
+ return request;
2532
+ };
2533
+ /**
2534
+ * Creates a POST request with the configured default settings.
2535
+ *
2536
+ * @param url - Optional URL path. If not provided, uses the base URL. If relative, resolves against base URL.
2537
+ * @returns A PostRequest instance ready to be executed
2538
+ * @example
2539
+ * ```typescript
2540
+ * const api = create.api().withBaseURL("https://api.example.com");
2541
+ * await api.post("/users").withBody({ name: "John" }).getJson();
2542
+ * ```
2543
+ */
2544
+ post = (url) => {
2545
+ const request = post(resolveURL(this.baseURL, url));
2546
+ if (this.modifiers)
2547
+ for (const modifier of this.modifiers)
2548
+ modifier(request);
2549
+ return request;
2550
+ };
2551
+ /**
2552
+ * Creates a PUT request with the configured default settings.
2553
+ *
2554
+ * @param url - Optional URL path. If not provided, uses the base URL. If relative, resolves against base URL.
2555
+ * @returns A PutRequest instance ready to be executed
2556
+ * @example
2557
+ * ```typescript
2558
+ * const api = create.api().withBaseURL("https://api.example.com");
2559
+ * await api.put("/users/123").withBody({ name: "Jane" }).getJson();
2560
+ * ```
2561
+ */
2562
+ put = (url) => {
2563
+ const request = put(resolveURL(this.baseURL, url));
2564
+ if (this.modifiers)
2565
+ for (const modifier of this.modifiers)
2566
+ modifier(request);
2567
+ return request;
2568
+ };
2569
+ /**
2570
+ * Creates a DELETE request with the configured default settings.
2571
+ *
2572
+ * @param url - Optional URL path. If not provided, uses the base URL. If relative, resolves against base URL.
2573
+ * @returns A DeleteRequest instance ready to be executed
2574
+ * @example
2575
+ * ```typescript
2576
+ * const api = create.api().withBaseURL("https://api.example.com");
2577
+ * await api.del("/users/123").getResponse();
2578
+ * ```
2579
+ */
2580
+ del = (url) => {
2581
+ const request = del(resolveURL(this.baseURL, url));
2582
+ if (this.modifiers)
2583
+ for (const modifier of this.modifiers)
2584
+ modifier(request);
2585
+ return request;
2586
+ };
2587
+ /**
2588
+ * Creates a PATCH request with the configured default settings.
2589
+ *
2590
+ * @param url - Optional URL path. If not provided, uses the base URL. If relative, resolves against base URL.
2591
+ * @returns A PatchRequest instance ready to be executed
2592
+ * @example
2593
+ * ```typescript
2594
+ * const api = create.api().withBaseURL("https://api.example.com");
2595
+ * await api.patch("/users/123").withBody({ name: "Updated" }).getJson();
2596
+ * ```
2597
+ */
2598
+ patch = (url) => {
2599
+ const request = patch(resolveURL(this.baseURL, url));
2600
+ if (this.modifiers)
2601
+ for (const modifier of this.modifiers)
2602
+ modifier(request);
2603
+ return request;
2604
+ };
2605
+ /**
2606
+ * Creates a HEAD request with the configured default settings.
2607
+ *
2608
+ * @param url - Optional URL path. If not provided, uses the base URL. If relative, resolves against base URL.
2609
+ * @returns A HeadRequest instance ready to be executed
2610
+ * @example
2611
+ * ```typescript
2612
+ * const api = create.api().withBaseURL("https://api.example.com");
2613
+ * const response = await api.head("/users").getResponse();
2614
+ * ```
2615
+ */
2616
+ head = (url) => {
2617
+ const request = head(resolveURL(this.baseURL, url));
2618
+ if (this.modifiers)
2619
+ for (const modifier of this.modifiers)
2620
+ modifier(request);
2621
+ return request;
2622
+ };
2623
+ /**
2624
+ * Creates an OPTIONS request with the configured default settings.
2625
+ *
2626
+ * @param url - Optional URL path. If not provided, uses the base URL. If relative, resolves against base URL.
2627
+ * @returns An OptionsRequest instance ready to be executed
2628
+ * @example
2629
+ * ```typescript
2630
+ * const api = create.api().withBaseURL("https://api.example.com");
2631
+ * await api.options("/users").getResponse();
2632
+ * ```
2633
+ */
2634
+ options = (url) => {
2635
+ const request = options(resolveURL(this.baseURL, url));
2636
+ if (this.modifiers)
2637
+ for (const modifier of this.modifiers)
2638
+ modifier(request);
2639
+ return request;
2640
+ };
2641
+ /**
2642
+ * Creates a new ApiBuilder instance with a Proxy that enables dynamic method forwarding.
2643
+ * The Proxy allows calling any `with*` method from BaseRequest on the API builder,
2644
+ * which will apply that configuration to all requests created through this builder.
2645
+ *
2646
+ * @internal
2647
+ */
2648
+ constructor() {
2649
+ const proxy = new Proxy(this, {
2650
+ get(target, prop) {
2651
+ const propertyValue = target[prop];
2652
+ if (propertyValue !== undefined) {
2653
+ if (typeof propertyValue === "function") {
2654
+ return (...args) => {
2655
+ const result = propertyValue.apply(target, args);
2656
+ return result === target ? proxy : result;
2657
+ };
2658
+ }
2659
+ return propertyValue;
2660
+ }
2661
+ if (typeof prop === "string" && prop.startsWith("with") && prop !== "withBaseURL") {
2662
+ if (prop === "withAbortController" || prop === "withBody" || prop === "withGraphQL")
2663
+ return undefined;
2664
+ const prototypeMethod = BaseRequest.prototype[prop];
2665
+ if (typeof prototypeMethod === "function") {
2666
+ return (...args) => {
2667
+ target.addModifier((request) => prototypeMethod.apply(request, args));
2668
+ return proxy;
2669
+ };
2670
+ }
2671
+ }
2672
+ return undefined;
2673
+ },
2674
+ });
2675
+ return proxy;
2676
+ }
2677
+ }
2678
+ /**
2679
+ * Creates a new API builder instance for configuring default request settings.
2680
+ * The builder allows you to set base URLs, authentication, headers, timeouts,
2681
+ * and other options that will be applied to all requests created through it.
2682
+ *
2683
+ * @returns A new ApiBuilder instance with all configuration methods available
2684
+ * @example
2685
+ * ```typescript
2686
+ * // Create an API instance with default configuration
2687
+ * const api = create.api()
2688
+ * .withBaseURL("https://api.example.com")
2689
+ * .withBearerToken("your-token")
2690
+ * .withTimeout(5000)
2691
+ * .withHeaders({ "X-Custom": "value" });
2692
+ *
2693
+ * // All requests will use these defaults
2694
+ * const users = await api.get("/users").getJson();
2695
+ * const newUser = await api.post("/users").withBody({ name: "John" }).getJson();
2696
+ * ```
2697
+ */
2698
+ function api() {
2699
+ return new ApiBuilder();
2700
+ }
2701
+
2459
2702
  /**
2460
2703
  * Main API object for creating HTTP requests
2461
2704
  * Provides factory methods for all HTTP methods and access to global configuration.
2462
2705
  */
2463
2706
  const create = {
2707
+ api,
2464
2708
  get,
2465
- post,
2466
2709
  put,
2467
2710
  del,
2711
+ post,
2468
2712
  patch,
2469
2713
  head,
2470
2714
  options,