create-request 1.4.3-rc.0 → 1.4.3-rc.2

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,9 +895,9 @@ 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
903
  if (/^https?:\/\//.test(trimmed)) {
@@ -902,7 +905,7 @@ class BaseRequest {
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,22 +1864,18 @@ 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
- else if (currentError instanceof ResponseWrapper && currentError.url && currentError.method) {
1894
- // 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, {
1896
- status: currentError.status,
1897
- });
1898
- }
1899
1872
  else {
1873
+ /* c8 ignore start */
1900
1874
  // Last resort: if we have no context at all, use the original error's context
1901
1875
  // This shouldn't happen in practice, but handle it gracefully
1902
1876
  const errorObj = interceptorError instanceof Error ? interceptorError : new Error(String(interceptorError));
1903
1877
  currentError = RequestError.networkError(error.url, error.method, errorObj);
1878
+ /* c8 ignore end */
1904
1879
  }
1905
1880
  }
1906
1881
  }
@@ -2135,7 +2110,7 @@ class BodyRequest extends BaseRequest {
2135
2110
  }
2136
2111
  catch (error) {
2137
2112
  const errorMessage = error instanceof Error ? error.message : String(error);
2138
- throw new RequestError(`JSON stringify failed: ${errorMessage}`, this.url, this.method);
2113
+ throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url, this.method);
2139
2114
  }
2140
2115
  }
2141
2116
  else {
@@ -2167,21 +2142,21 @@ class BodyRequest extends BaseRequest {
2167
2142
  */
2168
2143
  withGraphQL(query, variables, options) {
2169
2144
  if (typeof query !== "string" || query.length === 0) {
2170
- throw new RequestError("Invalid GraphQL query", this.url, this.method);
2145
+ throw new RequestError("Invalid query", this.url, this.method);
2171
2146
  }
2172
2147
  const graphQLBody = {
2173
2148
  query: query,
2174
2149
  };
2175
2150
  if (variables !== undefined) {
2176
2151
  if (typeof variables !== "object" || variables === null || Array.isArray(variables)) {
2177
- throw new RequestError("Invalid GraphQL variables", this.url, this.method);
2152
+ throw new RequestError("Invalid vars", this.url, this.method);
2178
2153
  }
2179
2154
  graphQLBody.variables = variables;
2180
2155
  }
2181
2156
  // Store GraphQL options if provided
2182
2157
  if (options !== undefined) {
2183
2158
  if (typeof options !== "object" || options === null || Array.isArray(options)) {
2184
- throw new RequestError("Invalid GraphQL options", this.url, this.method);
2159
+ throw new RequestError("Invalid options", this.url, this.method);
2185
2160
  }
2186
2161
  // Store only the known GraphQL options properties
2187
2162
  const opts = options;
@@ -2195,7 +2170,7 @@ class BodyRequest extends BaseRequest {
2195
2170
  }
2196
2171
  catch (error) {
2197
2172
  const errorMessage = error instanceof Error ? error.message : String(error);
2198
- throw new RequestError(`JSON stringify failed: ${errorMessage}`, this.url, this.method);
2173
+ throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url, this.method);
2199
2174
  }
2200
2175
  this.body = graphQLBody;
2201
2176
  this.bodyType = BodyType.JSON;
@@ -2678,7 +2653,7 @@ class ApiBuilder {
2678
2653
  return propertyValue;
2679
2654
  }
2680
2655
  if (typeof prop === "string" && prop.startsWith("with") && prop !== "withBaseURL") {
2681
- if (prop === "withAbortController" || prop === "withBody" || prop === "withGraphQL")
2656
+ if (prop === "withAbortController" || prop === "withBody" || prop === "withGraphQL" || prop === "withQueryParam" || prop === "withQueryParams")
2682
2657
  return undefined;
2683
2658
  const prototypeMethod = BaseRequest.prototype[prop];
2684
2659
  if (typeof prototypeMethod === "function") {
@@ -2734,5 +2709,5 @@ const create = {
2734
2709
  config: Config.getInstance(),
2735
2710
  };
2736
2711
 
2737
- export { CookieUtils, CredentialsPolicy, DeleteRequest, GetRequest, HeadRequest, HttpMethod, OptionsRequest, PatchRequest, PostRequest, PutRequest, RedirectMode, ReferrerPolicy, RequestError, RequestMode, RequestPriority, ResponseWrapper, SameSitePolicy, create as default };
2712
+ export { CacheMode, CookieUtils, CredentialsPolicy, DeleteRequest, GetRequest, HeadRequest, HttpMethod, OptionsRequest, PatchRequest, PostRequest, PutRequest, RedirectMode, ReferrerPolicy, RequestError, RequestMode, RequestPriority, ResponseWrapper, SameSitePolicy, api, create as default, del, get, head, options, patch, post, put };
2738
2713
  //# sourceMappingURL=index.esm.js.map