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.
- package/README.md +214 -9
- package/dist/library/RequestError.d.ts +1 -0
- package/dist/library/ResponseWrapper.d.ts +15 -9
- package/dist/library/apiBuilder.d.ts +182 -0
- package/dist/library/index.cjs +347 -103
- package/dist/library/index.cjs.map +1 -1
- package/dist/library/index.d.ts +4 -2
- package/dist/library/index.esm.js +346 -102
- package/dist/library/index.esm.js.map +1 -1
- package/dist/library/index.esm.min.js +1 -1
- package/dist/library/index.esm.min.js.map +1 -1
- package/dist/library/index.min.cjs +1 -1
- package/dist/library/index.min.cjs.map +1 -1
- package/package.json +15 -10
- package/dist/library/utils/sizeUtils.d.ts +0 -8
|
@@ -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
|
|
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 = `
|
|
165
|
+
message = `Timeout ${url}`;
|
|
166
166
|
}
|
|
167
167
|
else if (isDnsError) {
|
|
168
|
-
message = `
|
|
168
|
+
message = `DNS error ${url}`;
|
|
169
169
|
}
|
|
170
170
|
else if (isConnectionError) {
|
|
171
|
-
message = `
|
|
171
|
+
message = `Connection refused ${url}`;
|
|
172
172
|
}
|
|
173
173
|
else {
|
|
174
|
-
message = `Network error
|
|
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
|
-
|
|
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
|
|
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)
|
|
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.
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
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.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
response: this.response,
|
|
312
|
-
});
|
|
313
|
-
}
|
|
319
|
+
if (this.cachedText !== undefined)
|
|
320
|
+
return this.cachedText;
|
|
321
|
+
this.checkBodyNotConsumed();
|
|
314
322
|
try {
|
|
315
|
-
|
|
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
|
|
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.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
response: this.response,
|
|
341
|
-
});
|
|
342
|
-
}
|
|
346
|
+
if (this.cachedBlob !== undefined)
|
|
347
|
+
return this.cachedBlob;
|
|
348
|
+
this.checkBodyNotConsumed();
|
|
343
349
|
try {
|
|
344
|
-
|
|
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
|
|
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.
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
900
|
+
throw new RequestError("Invalid URL", url, this.method);
|
|
898
901
|
}
|
|
899
902
|
const trimmed = url.trim();
|
|
900
|
-
if (
|
|
903
|
+
if (/^https?:\/\//.test(trimmed)) {
|
|
901
904
|
try {
|
|
902
905
|
new URL(trimmed);
|
|
903
906
|
}
|
|
904
907
|
catch {
|
|
905
|
-
throw new RequestError(
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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("
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|