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
package/dist/library/index.cjs
CHANGED
|
@@ -85,7 +85,7 @@ exports.ReferrerPolicy = void 0;
|
|
|
85
85
|
/**
|
|
86
86
|
* Cache modes for fetch requests
|
|
87
87
|
*/
|
|
88
|
-
|
|
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
|
|
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 = `
|
|
167
|
+
message = `Timeout ${url}`;
|
|
168
168
|
}
|
|
169
169
|
else if (isDnsError) {
|
|
170
|
-
message = `
|
|
170
|
+
message = `DNS error ${url}`;
|
|
171
171
|
}
|
|
172
172
|
else if (isConnectionError) {
|
|
173
|
-
message = `
|
|
173
|
+
message = `Connection refused ${url}`;
|
|
174
174
|
}
|
|
175
175
|
else {
|
|
176
|
-
message = `Network error
|
|
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
|
-
|
|
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
|
|
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)
|
|
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.
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
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.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
response: this.response,
|
|
314
|
-
});
|
|
315
|
-
}
|
|
321
|
+
if (this.cachedText !== undefined)
|
|
322
|
+
return this.cachedText;
|
|
323
|
+
this.checkBodyNotConsumed();
|
|
316
324
|
try {
|
|
317
|
-
|
|
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
|
|
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.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
response: this.response,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
348
|
+
if (this.cachedBlob !== undefined)
|
|
349
|
+
return this.cachedBlob;
|
|
350
|
+
this.checkBodyNotConsumed();
|
|
345
351
|
try {
|
|
346
|
-
|
|
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
|
|
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.
|
|
369
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
902
|
+
throw new RequestError("Invalid URL", url, this.method);
|
|
900
903
|
}
|
|
901
904
|
const trimmed = url.trim();
|
|
902
|
-
if (
|
|
905
|
+
if (/^https?:\/\//.test(trimmed)) {
|
|
903
906
|
try {
|
|
904
907
|
new URL(trimmed);
|
|
905
908
|
}
|
|
906
909
|
catch {
|
|
907
|
-
throw new RequestError(
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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("
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|