create-request 1.0.0 → 1.2.0
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 +23 -49
- package/dist/library/BaseRequest.d.ts +53 -3
- package/dist/library/BodyRequest.d.ts +14 -2
- package/dist/library/ResponseWrapper.d.ts +35 -5
- package/dist/library/enums.d.ts +11 -0
- package/dist/library/index.cjs +239 -98
- package/dist/library/index.cjs.map +1 -1
- package/dist/library/index.d.ts +1 -1
- package/dist/library/index.esm.js +239 -98
- 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/dist/library/types.d.ts +10 -3
- package/package.json +4 -4
package/dist/library/index.cjs
CHANGED
|
@@ -82,6 +82,18 @@ exports.ReferrerPolicy = void 0;
|
|
|
82
82
|
ReferrerPolicy["NO_REFERRER_WHEN_DOWNGRADE"] = "no-referrer-when-downgrade";
|
|
83
83
|
ReferrerPolicy["STRICT_ORIGIN_WHEN_CROSS_ORIGIN"] = "strict-origin-when-cross-origin";
|
|
84
84
|
})(exports.ReferrerPolicy || (exports.ReferrerPolicy = {}));
|
|
85
|
+
/**
|
|
86
|
+
* Cache modes for fetch requests
|
|
87
|
+
*/
|
|
88
|
+
var CacheMode;
|
|
89
|
+
(function (CacheMode) {
|
|
90
|
+
CacheMode["RELOAD"] = "reload";
|
|
91
|
+
CacheMode["DEFAULT"] = "default";
|
|
92
|
+
CacheMode["NO_CACHE"] = "no-cache";
|
|
93
|
+
CacheMode["NO_STORE"] = "no-store";
|
|
94
|
+
CacheMode["FORCE_CACHE"] = "force-cache";
|
|
95
|
+
CacheMode["ONLY_IF_CACHED"] = "only-if-cached";
|
|
96
|
+
})(CacheMode || (CacheMode = {}));
|
|
85
97
|
|
|
86
98
|
class RequestError extends Error {
|
|
87
99
|
constructor(message, url, method, options = {}) {
|
|
@@ -178,11 +190,16 @@ class RequestError extends Error {
|
|
|
178
190
|
* Provides caching and conversion between different response formats.
|
|
179
191
|
*/
|
|
180
192
|
class ResponseWrapper {
|
|
181
|
-
constructor(response, url, method) {
|
|
193
|
+
constructor(response, url, method, graphQLOptions) {
|
|
182
194
|
this.bodyUsed = false;
|
|
183
195
|
this.response = response;
|
|
184
196
|
this.url = url;
|
|
185
197
|
this.method = method;
|
|
198
|
+
if (graphQLOptions) {
|
|
199
|
+
this.graphQLOptions = {
|
|
200
|
+
throwOnError: graphQLOptions.throwOnError,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
186
203
|
}
|
|
187
204
|
// Wrapper properties
|
|
188
205
|
get status() {
|
|
@@ -200,19 +217,51 @@ class ResponseWrapper {
|
|
|
200
217
|
get raw() {
|
|
201
218
|
return this.response;
|
|
202
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Check for GraphQL errors and throw if throwOnError is enabled
|
|
222
|
+
* @param data - The parsed JSON data
|
|
223
|
+
* @throws RequestError if GraphQL response contains errors and throwOnError is enabled
|
|
224
|
+
*/
|
|
225
|
+
checkGraphQLErrors(data) {
|
|
226
|
+
var _a;
|
|
227
|
+
if (!((_a = this.graphQLOptions) === null || _a === void 0 ? void 0 : _a.throwOnError) || typeof data !== "object" || data === null)
|
|
228
|
+
return;
|
|
229
|
+
const responseData = data;
|
|
230
|
+
if (!Array.isArray(responseData.errors) || responseData.errors.length === 0)
|
|
231
|
+
return;
|
|
232
|
+
const errors = responseData.errors;
|
|
233
|
+
const errorMessages = errors.map(x => typeof x === "string" ? x : x && typeof x === "object" && "message" in x ? String(x.message || "Unknown error") : String(x));
|
|
234
|
+
const errorMessage = errorMessages.join(", ");
|
|
235
|
+
throw new RequestError(`GraphQL request failed with errors: ${errorMessage}`, this.url || "", this.method || "", {
|
|
236
|
+
status: this.response.status,
|
|
237
|
+
response: this.response,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
203
240
|
/**
|
|
204
241
|
* Parse the response body as JSON
|
|
205
242
|
* Caches the result for subsequent calls.
|
|
243
|
+
* If GraphQL options are set with throwOnError=true, will check for GraphQL errors and throw.
|
|
206
244
|
*
|
|
207
245
|
* @returns The parsed JSON data
|
|
208
|
-
* @throws
|
|
246
|
+
* @throws {RequestError} When the request fails, JSON parsing fails, GraphQL errors occur (if throwOnError enabled), or body is already consumed
|
|
209
247
|
*
|
|
210
248
|
* @example
|
|
211
249
|
* const data = await response.getJson();
|
|
212
250
|
* console.log(data.items);
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* // Error handling - errors are always RequestError
|
|
254
|
+
* try {
|
|
255
|
+
* const data = await response.getJson();
|
|
256
|
+
* } catch (error) {
|
|
257
|
+
* if (error instanceof RequestError) {
|
|
258
|
+
* console.log(error.status, error.url, error.method);
|
|
259
|
+
* }
|
|
260
|
+
* }
|
|
213
261
|
*/
|
|
214
262
|
async getJson() {
|
|
215
263
|
if (this.jsonCache !== undefined) {
|
|
264
|
+
this.checkGraphQLErrors(this.jsonCache);
|
|
216
265
|
return this.jsonCache;
|
|
217
266
|
}
|
|
218
267
|
if (this.bodyUsed) {
|
|
@@ -221,40 +270,43 @@ class ResponseWrapper {
|
|
|
221
270
|
try {
|
|
222
271
|
const parsed = JSON.parse(this.textCache);
|
|
223
272
|
this.jsonCache = parsed;
|
|
273
|
+
this.checkGraphQLErrors(parsed);
|
|
224
274
|
return parsed;
|
|
225
275
|
}
|
|
226
|
-
catch (
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
throw
|
|
230
|
-
response: this.response,
|
|
231
|
-
});
|
|
276
|
+
catch (error) {
|
|
277
|
+
// Re-throw RequestErrors from checkGraphQLErrors without wrapping
|
|
278
|
+
if (error instanceof RequestError) {
|
|
279
|
+
throw error;
|
|
232
280
|
}
|
|
233
|
-
|
|
281
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
282
|
+
throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url || "", this.method || "", {
|
|
283
|
+
status: this.response.status,
|
|
284
|
+
response: this.response,
|
|
285
|
+
});
|
|
234
286
|
}
|
|
235
287
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
throw new Error("Body already consumed");
|
|
288
|
+
throw new RequestError("Body already consumed", this.url || "", this.method || "", {
|
|
289
|
+
status: this.response.status,
|
|
290
|
+
response: this.response,
|
|
291
|
+
});
|
|
242
292
|
}
|
|
243
293
|
this.bodyUsed = true;
|
|
244
294
|
try {
|
|
245
295
|
const parsed = await this.response.json();
|
|
246
296
|
this.jsonCache = parsed;
|
|
297
|
+
this.checkGraphQLErrors(parsed);
|
|
247
298
|
return parsed;
|
|
248
299
|
}
|
|
249
|
-
catch (
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
throw
|
|
253
|
-
status: this.response.status,
|
|
254
|
-
response: this.response,
|
|
255
|
-
});
|
|
300
|
+
catch (error) {
|
|
301
|
+
// Re-throw RequestErrors from checkGraphQLErrors without wrapping
|
|
302
|
+
if (error instanceof RequestError) {
|
|
303
|
+
throw error;
|
|
256
304
|
}
|
|
257
|
-
|
|
305
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
306
|
+
throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url || "", this.method || "", {
|
|
307
|
+
status: this.response.status,
|
|
308
|
+
response: this.response,
|
|
309
|
+
});
|
|
258
310
|
}
|
|
259
311
|
}
|
|
260
312
|
/**
|
|
@@ -263,7 +315,7 @@ class ResponseWrapper {
|
|
|
263
315
|
* if the body has already been consumed in those formats.
|
|
264
316
|
*
|
|
265
317
|
* @returns The response text
|
|
266
|
-
* @throws
|
|
318
|
+
* @throws {RequestError} When reading fails or the response has already been consumed in an incompatible format
|
|
267
319
|
*
|
|
268
320
|
* @example
|
|
269
321
|
* const text = await response.getText();
|
|
@@ -286,20 +338,16 @@ class ResponseWrapper {
|
|
|
286
338
|
}
|
|
287
339
|
catch (e) {
|
|
288
340
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
throw e;
|
|
341
|
+
throw new RequestError(`Cannot convert to text: ${errorMessage}`, this.url || "", this.method || "", {
|
|
342
|
+
status: this.response.status,
|
|
343
|
+
response: this.response,
|
|
344
|
+
});
|
|
295
345
|
}
|
|
296
346
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
throw new Error("Cannot convert to text");
|
|
347
|
+
throw new RequestError("Cannot convert to text", this.url || "", this.method || "", {
|
|
348
|
+
status: this.response.status,
|
|
349
|
+
response: this.response,
|
|
350
|
+
});
|
|
303
351
|
}
|
|
304
352
|
this.bodyUsed = true;
|
|
305
353
|
try {
|
|
@@ -308,13 +356,10 @@ class ResponseWrapper {
|
|
|
308
356
|
}
|
|
309
357
|
catch (e) {
|
|
310
358
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
throw e;
|
|
359
|
+
throw new RequestError(`Read failed: ${errorMessage}`, this.url || "", this.method || "", {
|
|
360
|
+
status: this.response.status,
|
|
361
|
+
response: this.response,
|
|
362
|
+
});
|
|
318
363
|
}
|
|
319
364
|
}
|
|
320
365
|
/**
|
|
@@ -323,7 +368,7 @@ class ResponseWrapper {
|
|
|
323
368
|
* if the body has already been consumed as text.
|
|
324
369
|
*
|
|
325
370
|
* @returns The response as a Blob
|
|
326
|
-
* @throws
|
|
371
|
+
* @throws {RequestError} When reading fails or the response has already been consumed in an incompatible format
|
|
327
372
|
*
|
|
328
373
|
* @example
|
|
329
374
|
* const blob = await response.getBlob();
|
|
@@ -339,12 +384,10 @@ class ResponseWrapper {
|
|
|
339
384
|
this.blobCache = new Blob([this.textCache], { type: "text/plain" });
|
|
340
385
|
return this.blobCache;
|
|
341
386
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
throw new Error("Cannot convert to Blob");
|
|
387
|
+
throw new RequestError("Cannot convert to Blob", this.url || "", this.method || "", {
|
|
388
|
+
status: this.response.status,
|
|
389
|
+
response: this.response,
|
|
390
|
+
});
|
|
348
391
|
}
|
|
349
392
|
this.bodyUsed = true;
|
|
350
393
|
try {
|
|
@@ -353,13 +396,10 @@ class ResponseWrapper {
|
|
|
353
396
|
}
|
|
354
397
|
catch (e) {
|
|
355
398
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
throw e;
|
|
399
|
+
throw new RequestError(`Read failed: ${errorMessage}`, this.url || "", this.method || "", {
|
|
400
|
+
status: this.response.status,
|
|
401
|
+
response: this.response,
|
|
402
|
+
});
|
|
363
403
|
}
|
|
364
404
|
}
|
|
365
405
|
/**
|
|
@@ -367,7 +407,7 @@ class ResponseWrapper {
|
|
|
367
407
|
* This method consumes the body and should only be called once.
|
|
368
408
|
*
|
|
369
409
|
* @returns The response body as a ReadableStream or null
|
|
370
|
-
* @throws
|
|
410
|
+
* @throws {RequestError} When the response body has already been consumed
|
|
371
411
|
*
|
|
372
412
|
* @example
|
|
373
413
|
* const stream = response.getBody();
|
|
@@ -378,12 +418,10 @@ class ResponseWrapper {
|
|
|
378
418
|
*/
|
|
379
419
|
getBody() {
|
|
380
420
|
if (this.bodyUsed) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
throw new Error("Body already consumed");
|
|
421
|
+
throw new RequestError("Body already consumed", this.url || "", this.method || "", {
|
|
422
|
+
status: this.response.status,
|
|
423
|
+
response: this.response,
|
|
424
|
+
});
|
|
387
425
|
}
|
|
388
426
|
this.bodyUsed = true;
|
|
389
427
|
return this.response.body;
|
|
@@ -394,6 +432,7 @@ class ResponseWrapper {
|
|
|
394
432
|
*
|
|
395
433
|
* @param selector - Optional function to extract and transform data
|
|
396
434
|
* @returns A promise that resolves to the selected data
|
|
435
|
+
* @throws {RequestError} When the request fails, JSON parsing fails, or the selector throws an error
|
|
397
436
|
*
|
|
398
437
|
* @example
|
|
399
438
|
* // Get full response
|
|
@@ -401,6 +440,16 @@ class ResponseWrapper {
|
|
|
401
440
|
*
|
|
402
441
|
* // Extract specific data
|
|
403
442
|
* const users = await response.getData(data => data.results.users);
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* // Error handling - errors are always RequestError
|
|
446
|
+
* try {
|
|
447
|
+
* const data = await response.getData();
|
|
448
|
+
* } catch (error) {
|
|
449
|
+
* if (error instanceof RequestError) {
|
|
450
|
+
* console.log(error.status, error.url, error.method);
|
|
451
|
+
* }
|
|
452
|
+
* }
|
|
404
453
|
*/
|
|
405
454
|
async getData(selector) {
|
|
406
455
|
try {
|
|
@@ -419,20 +468,15 @@ class ResponseWrapper {
|
|
|
419
468
|
// Enhance selector errors with context
|
|
420
469
|
if (selector) {
|
|
421
470
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
throw new Error(`Data selector failed: ${errorMessage}`);
|
|
471
|
+
throw new RequestError(`Data selector failed: ${errorMessage}`, this.url || "", this.method || "", {
|
|
472
|
+
status: this.response.status,
|
|
473
|
+
response: this.response,
|
|
474
|
+
});
|
|
428
475
|
}
|
|
429
476
|
// If we get here and it's not a RequestError, wrap it
|
|
430
477
|
// This should rarely happen as getJson() should throw RequestError
|
|
431
478
|
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
432
|
-
|
|
433
|
-
throw RequestError.networkError(this.url, this.method, errorObj);
|
|
434
|
-
}
|
|
435
|
-
throw errorObj;
|
|
479
|
+
throw RequestError.networkError(this.url || "", this.method || "", errorObj);
|
|
436
480
|
}
|
|
437
481
|
}
|
|
438
482
|
}
|
|
@@ -840,6 +884,13 @@ class BaseRequest {
|
|
|
840
884
|
this.errorInterceptors = [];
|
|
841
885
|
this.url = url;
|
|
842
886
|
}
|
|
887
|
+
/**
|
|
888
|
+
* Get GraphQL options if set (only for BodyRequest subclasses)
|
|
889
|
+
* @returns GraphQL options or undefined
|
|
890
|
+
*/
|
|
891
|
+
getGraphQLOptions() {
|
|
892
|
+
return undefined;
|
|
893
|
+
}
|
|
843
894
|
/**
|
|
844
895
|
* Creates a fluent API for setting enum-based options
|
|
845
896
|
* Combines direct setter with convenience methods
|
|
@@ -925,14 +976,14 @@ class BaseRequest {
|
|
|
925
976
|
*
|
|
926
977
|
* @param timeout - The timeout in milliseconds
|
|
927
978
|
* @returns The request instance for chaining
|
|
928
|
-
* @throws
|
|
979
|
+
* @throws RequestError if timeout is not a positive number
|
|
929
980
|
*
|
|
930
981
|
* @example
|
|
931
982
|
* request.withTimeout(5000); // 5 seconds timeout
|
|
932
983
|
*/
|
|
933
984
|
withTimeout(timeout) {
|
|
934
985
|
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
935
|
-
throw new
|
|
986
|
+
throw new RequestError("Timeout must be a positive number", this.url, this.method);
|
|
936
987
|
this.requestOptions.timeout = timeout;
|
|
937
988
|
return this;
|
|
938
989
|
}
|
|
@@ -941,14 +992,14 @@ class BaseRequest {
|
|
|
941
992
|
*
|
|
942
993
|
* @param retries - Number of retry attempts before failing
|
|
943
994
|
* @returns The request instance for chaining
|
|
944
|
-
* @throws
|
|
995
|
+
* @throws RequestError if retries is not a non-negative integer
|
|
945
996
|
*
|
|
946
997
|
* @example
|
|
947
998
|
* request.withRetries(3); // Retry up to 3 times
|
|
948
999
|
*/
|
|
949
1000
|
withRetries(retries) {
|
|
950
1001
|
if (!Number.isInteger(retries) || retries < 0)
|
|
951
|
-
throw new
|
|
1002
|
+
throw new RequestError("Retry count must be a non-negative integer", this.url, this.method);
|
|
952
1003
|
this.requestOptions.retries = retries;
|
|
953
1004
|
return this;
|
|
954
1005
|
}
|
|
@@ -1057,6 +1108,35 @@ class BaseRequest {
|
|
|
1057
1108
|
AUTO: exports.RequestPriority.AUTO,
|
|
1058
1109
|
});
|
|
1059
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Sets the integrity hash for subresource integrity verification
|
|
1113
|
+
* @param integrity - The integrity hash string (e.g., "sha256-...")
|
|
1114
|
+
* @returns The instance for chaining
|
|
1115
|
+
* @example
|
|
1116
|
+
* request.withIntegrity("sha256-abcdef1234567890...")
|
|
1117
|
+
*/
|
|
1118
|
+
withIntegrity(integrity) {
|
|
1119
|
+
this.requestOptions.integrity = integrity;
|
|
1120
|
+
return this;
|
|
1121
|
+
}
|
|
1122
|
+
/**
|
|
1123
|
+
* Sets cache mode - supports both direct call and fluent API
|
|
1124
|
+
* @example
|
|
1125
|
+
* request.withCache("no-cache")
|
|
1126
|
+
* request.withCache(CacheMode.NO_CACHE)
|
|
1127
|
+
* request.withCache.NO_CACHE()
|
|
1128
|
+
*/
|
|
1129
|
+
get withCache() {
|
|
1130
|
+
const cacheOptions = {
|
|
1131
|
+
DEFAULT: "default",
|
|
1132
|
+
NO_STORE: "no-store",
|
|
1133
|
+
RELOAD: "reload",
|
|
1134
|
+
NO_CACHE: "no-cache",
|
|
1135
|
+
FORCE_CACHE: "force-cache",
|
|
1136
|
+
ONLY_IF_CACHED: "only-if-cached",
|
|
1137
|
+
};
|
|
1138
|
+
return this.createFluentSetter("cache", cacheOptions);
|
|
1139
|
+
}
|
|
1060
1140
|
/**
|
|
1061
1141
|
* Adds query parameters to the request URL
|
|
1062
1142
|
* @param params - An object containing the query parameters
|
|
@@ -1155,7 +1235,7 @@ class BaseRequest {
|
|
|
1155
1235
|
if (typeof Buffer !== "undefined")
|
|
1156
1236
|
return Buffer.from(str).toString("base64");
|
|
1157
1237
|
// Fallback (should never happen in modern environments)
|
|
1158
|
-
throw new
|
|
1238
|
+
throw new RequestError("Base64 encoding is not supported", this.url, this.method);
|
|
1159
1239
|
}
|
|
1160
1240
|
/**
|
|
1161
1241
|
* Sets the bearer token for authentication
|
|
@@ -1336,9 +1416,20 @@ class BaseRequest {
|
|
|
1336
1416
|
* Execute the request and parse the response as JSON
|
|
1337
1417
|
*
|
|
1338
1418
|
* @returns A promise that resolves to the parsed JSON data
|
|
1419
|
+
* @throws {RequestError} When the request fails, JSON parsing fails, GraphQL errors occur (if throwOnError enabled), or body is already consumed
|
|
1339
1420
|
*
|
|
1340
1421
|
* @example
|
|
1341
1422
|
* const users = await request.getJson<User[]>();
|
|
1423
|
+
*
|
|
1424
|
+
* @example
|
|
1425
|
+
* // Error handling - errors are always RequestError
|
|
1426
|
+
* try {
|
|
1427
|
+
* const data = await request.getJson();
|
|
1428
|
+
* } catch (error) {
|
|
1429
|
+
* if (error instanceof RequestError) {
|
|
1430
|
+
* console.log(error.status, error.url, error.method);
|
|
1431
|
+
* }
|
|
1432
|
+
* }
|
|
1342
1433
|
*/
|
|
1343
1434
|
async getJson() {
|
|
1344
1435
|
const response = await this.getResponse();
|
|
@@ -1386,6 +1477,7 @@ class BaseRequest {
|
|
|
1386
1477
|
*
|
|
1387
1478
|
* @param selector - Optional function to extract and transform data
|
|
1388
1479
|
* @returns A promise that resolves to the selected data
|
|
1480
|
+
* @throws {RequestError} When the request fails, JSON parsing fails, or the selector throws an error
|
|
1389
1481
|
*
|
|
1390
1482
|
* @example
|
|
1391
1483
|
* // Get full response
|
|
@@ -1393,6 +1485,16 @@ class BaseRequest {
|
|
|
1393
1485
|
*
|
|
1394
1486
|
* // Extract specific data
|
|
1395
1487
|
* const users = await request.getData(data => data.results.users);
|
|
1488
|
+
*
|
|
1489
|
+
* @example
|
|
1490
|
+
* // Error handling - errors are always RequestError
|
|
1491
|
+
* try {
|
|
1492
|
+
* const data = await request.getData();
|
|
1493
|
+
* } catch (error) {
|
|
1494
|
+
* if (error instanceof RequestError) {
|
|
1495
|
+
* console.log(error.status, error.url, error.method);
|
|
1496
|
+
* }
|
|
1497
|
+
* }
|
|
1396
1498
|
*/
|
|
1397
1499
|
async getData(selector) {
|
|
1398
1500
|
const response = await this.getResponse();
|
|
@@ -1442,7 +1544,8 @@ class BaseRequest {
|
|
|
1442
1544
|
* @returns The URL with query parameters appended
|
|
1443
1545
|
*/
|
|
1444
1546
|
formatUrlWithQueryParams(url) {
|
|
1445
|
-
|
|
1547
|
+
const queryString = this.queryParams.toString();
|
|
1548
|
+
if (!queryString) {
|
|
1446
1549
|
return url;
|
|
1447
1550
|
}
|
|
1448
1551
|
try {
|
|
@@ -1458,7 +1561,7 @@ class BaseRequest {
|
|
|
1458
1561
|
// Handle relative URLs
|
|
1459
1562
|
const hasExistingParams = url.includes("?");
|
|
1460
1563
|
const separator = hasExistingParams ? "&" : "?";
|
|
1461
|
-
return `${url}${separator}${
|
|
1564
|
+
return `${url}${separator}${queryString}`;
|
|
1462
1565
|
}
|
|
1463
1566
|
}
|
|
1464
1567
|
/**
|
|
@@ -1507,8 +1610,7 @@ class BaseRequest {
|
|
|
1507
1610
|
}
|
|
1508
1611
|
catch (error) {
|
|
1509
1612
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1510
|
-
|
|
1511
|
-
throw new RequestError(`Interceptor failed: ${errorMessage}`, currentConfig.url, currentConfig.method);
|
|
1613
|
+
throw new RequestError(`Request interceptor failed: ${errorMessage}`, currentConfig.url, currentConfig.method);
|
|
1512
1614
|
}
|
|
1513
1615
|
}
|
|
1514
1616
|
return currentConfig;
|
|
@@ -1530,12 +1632,9 @@ class BaseRequest {
|
|
|
1530
1632
|
}
|
|
1531
1633
|
catch (error) {
|
|
1532
1634
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
}
|
|
1537
|
-
// Fallback to generic Error if no context available (shouldn't happen in practice)
|
|
1538
|
-
throw new Error(`Interceptor failed: ${errorMessage}`);
|
|
1635
|
+
const url = currentResponse.url || "";
|
|
1636
|
+
const method = currentResponse.method || "";
|
|
1637
|
+
throw new RequestError(`Response interceptor failed: ${errorMessage}`, url, method);
|
|
1539
1638
|
}
|
|
1540
1639
|
}
|
|
1541
1640
|
return currentResponse;
|
|
@@ -1569,11 +1668,16 @@ class BaseRequest {
|
|
|
1569
1668
|
const errorMessage = interceptorError instanceof Error ? interceptorError.message : String(interceptorError);
|
|
1570
1669
|
// Always wrap in RequestError when we have context
|
|
1571
1670
|
if (currentError instanceof RequestError) {
|
|
1572
|
-
currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method
|
|
1671
|
+
currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method, {
|
|
1672
|
+
status: currentError.status,
|
|
1673
|
+
response: currentError.response,
|
|
1674
|
+
});
|
|
1573
1675
|
}
|
|
1574
1676
|
else if (currentError instanceof ResponseWrapper && currentError.url && currentError.method) {
|
|
1575
1677
|
// If it's a ResponseWrapper, we can still create a proper RequestError from its context
|
|
1576
|
-
currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method
|
|
1678
|
+
currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method, {
|
|
1679
|
+
status: currentError.status,
|
|
1680
|
+
});
|
|
1577
1681
|
}
|
|
1578
1682
|
else {
|
|
1579
1683
|
// Last resort: if we have no context at all, use the original error's context
|
|
@@ -1674,6 +1778,8 @@ class BaseRequest {
|
|
|
1674
1778
|
referrerPolicy: fetchOptions.referrerPolicy,
|
|
1675
1779
|
keepalive: fetchOptions.keepalive,
|
|
1676
1780
|
priority: extendedOptions.priority,
|
|
1781
|
+
integrity: fetchOptions.integrity,
|
|
1782
|
+
cache: fetchOptions.cache,
|
|
1677
1783
|
};
|
|
1678
1784
|
}
|
|
1679
1785
|
/**
|
|
@@ -1684,6 +1790,12 @@ class BaseRequest {
|
|
|
1684
1790
|
if (config.body !== undefined) {
|
|
1685
1791
|
fetchOptions.body = config.body;
|
|
1686
1792
|
}
|
|
1793
|
+
if (config.integrity !== undefined) {
|
|
1794
|
+
fetchOptions.integrity = config.integrity;
|
|
1795
|
+
}
|
|
1796
|
+
if (config.cache !== undefined) {
|
|
1797
|
+
fetchOptions.cache = config.cache;
|
|
1798
|
+
}
|
|
1687
1799
|
}
|
|
1688
1800
|
async executeRequest(url, fetchOptions) {
|
|
1689
1801
|
const method = typeof fetchOptions.method === "string" ? fetchOptions.method : "GET";
|
|
@@ -1695,7 +1807,8 @@ class BaseRequest {
|
|
|
1695
1807
|
const interceptorResult = await this.runRequestInterceptors(requestConfig);
|
|
1696
1808
|
// If interceptor returned a Response, short-circuit and wrap it
|
|
1697
1809
|
if (interceptorResult instanceof Response) {
|
|
1698
|
-
const
|
|
1810
|
+
const graphQLOptions = this.getGraphQLOptions();
|
|
1811
|
+
const wrappedResponse = new ResponseWrapper(interceptorResult, this.url, this.method, graphQLOptions);
|
|
1699
1812
|
return await this.runResponseInterceptors(wrappedResponse);
|
|
1700
1813
|
}
|
|
1701
1814
|
// Update fetchOptions with interceptor modifications
|
|
@@ -1731,7 +1844,8 @@ class BaseRequest {
|
|
|
1731
1844
|
if (!response.ok) {
|
|
1732
1845
|
throw RequestError.fromResponse(response, url, method);
|
|
1733
1846
|
}
|
|
1734
|
-
const
|
|
1847
|
+
const graphQLOptions = this.getGraphQLOptions();
|
|
1848
|
+
const wrappedResponse = new ResponseWrapper(response, url, method, graphQLOptions);
|
|
1735
1849
|
return await this.runResponseInterceptors(wrappedResponse);
|
|
1736
1850
|
}
|
|
1737
1851
|
catch (error) {
|
|
@@ -1764,6 +1878,7 @@ class BaseRequest {
|
|
|
1764
1878
|
class BodyRequest extends BaseRequest {
|
|
1765
1879
|
constructor(url) {
|
|
1766
1880
|
super(url);
|
|
1881
|
+
this.graphQLOptions = undefined;
|
|
1767
1882
|
}
|
|
1768
1883
|
/**
|
|
1769
1884
|
* Sets the body of the request
|
|
@@ -1786,7 +1901,8 @@ class BodyRequest extends BaseRequest {
|
|
|
1786
1901
|
JSON.stringify(body);
|
|
1787
1902
|
}
|
|
1788
1903
|
catch (error) {
|
|
1789
|
-
|
|
1904
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1905
|
+
throw new RequestError(`JSON stringify failed: ${errorMessage}`, this.url, this.method);
|
|
1790
1906
|
}
|
|
1791
1907
|
}
|
|
1792
1908
|
else {
|
|
@@ -1800,6 +1916,7 @@ class BodyRequest extends BaseRequest {
|
|
|
1800
1916
|
*
|
|
1801
1917
|
* @param query - The GraphQL query or mutation string
|
|
1802
1918
|
* @param variables - Optional variables object to pass with the query
|
|
1919
|
+
* @param options - Optional GraphQL-specific options
|
|
1803
1920
|
* @returns The request instance for chaining
|
|
1804
1921
|
*
|
|
1805
1922
|
* @example
|
|
@@ -1809,13 +1926,18 @@ class BodyRequest extends BaseRequest {
|
|
|
1809
1926
|
* @example
|
|
1810
1927
|
* const request = new PostRequest('/graphql')
|
|
1811
1928
|
* .withGraphQL('mutation { createUser(name: $name) { id } }', { name: 'John' });
|
|
1929
|
+
*
|
|
1930
|
+
* @example
|
|
1931
|
+
* // Throw an error if the GraphQL response contains errors
|
|
1932
|
+
* const request = new PostRequest('/graphql')
|
|
1933
|
+
* .withGraphQL('query { user { id } }', undefined, { throwOnError: true });
|
|
1812
1934
|
*/
|
|
1813
|
-
withGraphQL(query, variables) {
|
|
1814
|
-
if (typeof query !== "string" || query.
|
|
1935
|
+
withGraphQL(query, variables, options) {
|
|
1936
|
+
if (typeof query !== "string" || query.length === 0) {
|
|
1815
1937
|
throw new RequestError("Invalid GraphQL query", this.url, this.method);
|
|
1816
1938
|
}
|
|
1817
1939
|
const graphQLBody = {
|
|
1818
|
-
query: query
|
|
1940
|
+
query: query,
|
|
1819
1941
|
};
|
|
1820
1942
|
if (variables !== undefined) {
|
|
1821
1943
|
if (typeof variables !== "object" || variables === null || Array.isArray(variables)) {
|
|
@@ -1823,12 +1945,24 @@ class BodyRequest extends BaseRequest {
|
|
|
1823
1945
|
}
|
|
1824
1946
|
graphQLBody.variables = variables;
|
|
1825
1947
|
}
|
|
1948
|
+
// Store GraphQL options if provided
|
|
1949
|
+
if (options !== undefined) {
|
|
1950
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
1951
|
+
throw new RequestError("Invalid GraphQL options", this.url, this.method);
|
|
1952
|
+
}
|
|
1953
|
+
// Store only the known GraphQL options properties
|
|
1954
|
+
const opts = options;
|
|
1955
|
+
this.graphQLOptions = {
|
|
1956
|
+
throwOnError: typeof opts.throwOnError === "boolean" ? opts.throwOnError : undefined,
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1826
1959
|
// Validate JSON is stringifiable early
|
|
1827
1960
|
try {
|
|
1828
1961
|
JSON.stringify(graphQLBody);
|
|
1829
1962
|
}
|
|
1830
1963
|
catch (error) {
|
|
1831
|
-
|
|
1964
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1965
|
+
throw new RequestError(`JSON stringify failed: ${errorMessage}`, this.url, this.method);
|
|
1832
1966
|
}
|
|
1833
1967
|
this.body = graphQLBody;
|
|
1834
1968
|
this.bodyType = BodyType.JSON;
|
|
@@ -1851,6 +1985,13 @@ class BodyRequest extends BaseRequest {
|
|
|
1851
1985
|
this.withContentType(contentType);
|
|
1852
1986
|
}
|
|
1853
1987
|
}
|
|
1988
|
+
/**
|
|
1989
|
+
* Get the GraphQL options if set
|
|
1990
|
+
* @returns The GraphQL options or undefined
|
|
1991
|
+
*/
|
|
1992
|
+
getGraphQLOptions() {
|
|
1993
|
+
return this.graphQLOptions;
|
|
1994
|
+
}
|
|
1854
1995
|
/**
|
|
1855
1996
|
* Execute the request and return the ResponseWrapper
|
|
1856
1997
|
* Overrides the base implementation to add body handling
|