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