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