create-request 1.0.0 → 1.1.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 +22 -48
- package/dist/library/BaseRequest.d.ts +30 -3
- package/dist/library/BodyRequest.d.ts +14 -2
- package/dist/library/ResponseWrapper.d.ts +35 -5
- package/dist/library/index.cjs +190 -98
- package/dist/library/index.cjs.map +1 -1
- package/dist/library/index.d.ts +1 -1
- package/dist/library/index.esm.js +190 -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 +3 -0
- package/package.json +3 -3
package/dist/library/index.cjs
CHANGED
|
@@ -178,11 +178,16 @@ class RequestError extends Error {
|
|
|
178
178
|
* Provides caching and conversion between different response formats.
|
|
179
179
|
*/
|
|
180
180
|
class ResponseWrapper {
|
|
181
|
-
constructor(response, url, method) {
|
|
181
|
+
constructor(response, url, method, graphQLOptions) {
|
|
182
182
|
this.bodyUsed = false;
|
|
183
183
|
this.response = response;
|
|
184
184
|
this.url = url;
|
|
185
185
|
this.method = method;
|
|
186
|
+
if (graphQLOptions) {
|
|
187
|
+
this.graphQLOptions = {
|
|
188
|
+
throwOnError: graphQLOptions.throwOnError,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
186
191
|
}
|
|
187
192
|
// Wrapper properties
|
|
188
193
|
get status() {
|
|
@@ -200,19 +205,51 @@ class ResponseWrapper {
|
|
|
200
205
|
get raw() {
|
|
201
206
|
return this.response;
|
|
202
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Check for GraphQL errors and throw if throwOnError is enabled
|
|
210
|
+
* @param data - The parsed JSON data
|
|
211
|
+
* @throws RequestError if GraphQL response contains errors and throwOnError is enabled
|
|
212
|
+
*/
|
|
213
|
+
checkGraphQLErrors(data) {
|
|
214
|
+
var _a;
|
|
215
|
+
if (!((_a = this.graphQLOptions) === null || _a === void 0 ? void 0 : _a.throwOnError) || typeof data !== "object" || data === null)
|
|
216
|
+
return;
|
|
217
|
+
const responseData = data;
|
|
218
|
+
if (!Array.isArray(responseData.errors) || responseData.errors.length === 0)
|
|
219
|
+
return;
|
|
220
|
+
const errors = responseData.errors;
|
|
221
|
+
const errorMessages = errors.map(x => typeof x === "string" ? x : x && typeof x === "object" && "message" in x ? String(x.message || "Unknown error") : String(x));
|
|
222
|
+
const errorMessage = errorMessages.join(", ");
|
|
223
|
+
throw new RequestError(`GraphQL request failed with errors: ${errorMessage}`, this.url || "", this.method || "", {
|
|
224
|
+
status: this.response.status,
|
|
225
|
+
response: this.response,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
203
228
|
/**
|
|
204
229
|
* Parse the response body as JSON
|
|
205
230
|
* Caches the result for subsequent calls.
|
|
231
|
+
* If GraphQL options are set with throwOnError=true, will check for GraphQL errors and throw.
|
|
206
232
|
*
|
|
207
233
|
* @returns The parsed JSON data
|
|
208
|
-
* @throws
|
|
234
|
+
* @throws {RequestError} When the request fails, JSON parsing fails, GraphQL errors occur (if throwOnError enabled), or body is already consumed
|
|
209
235
|
*
|
|
210
236
|
* @example
|
|
211
237
|
* const data = await response.getJson();
|
|
212
238
|
* console.log(data.items);
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* // Error handling - errors are always RequestError
|
|
242
|
+
* try {
|
|
243
|
+
* const data = await response.getJson();
|
|
244
|
+
* } catch (error) {
|
|
245
|
+
* if (error instanceof RequestError) {
|
|
246
|
+
* console.log(error.status, error.url, error.method);
|
|
247
|
+
* }
|
|
248
|
+
* }
|
|
213
249
|
*/
|
|
214
250
|
async getJson() {
|
|
215
251
|
if (this.jsonCache !== undefined) {
|
|
252
|
+
this.checkGraphQLErrors(this.jsonCache);
|
|
216
253
|
return this.jsonCache;
|
|
217
254
|
}
|
|
218
255
|
if (this.bodyUsed) {
|
|
@@ -221,40 +258,43 @@ class ResponseWrapper {
|
|
|
221
258
|
try {
|
|
222
259
|
const parsed = JSON.parse(this.textCache);
|
|
223
260
|
this.jsonCache = parsed;
|
|
261
|
+
this.checkGraphQLErrors(parsed);
|
|
224
262
|
return parsed;
|
|
225
263
|
}
|
|
226
|
-
catch (
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
throw
|
|
230
|
-
response: this.response,
|
|
231
|
-
});
|
|
264
|
+
catch (error) {
|
|
265
|
+
// Re-throw RequestErrors from checkGraphQLErrors without wrapping
|
|
266
|
+
if (error instanceof RequestError) {
|
|
267
|
+
throw error;
|
|
232
268
|
}
|
|
233
|
-
|
|
269
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
270
|
+
throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url || "", this.method || "", {
|
|
271
|
+
status: this.response.status,
|
|
272
|
+
response: this.response,
|
|
273
|
+
});
|
|
234
274
|
}
|
|
235
275
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
throw new Error("Body already consumed");
|
|
276
|
+
throw new RequestError("Body already consumed", this.url || "", this.method || "", {
|
|
277
|
+
status: this.response.status,
|
|
278
|
+
response: this.response,
|
|
279
|
+
});
|
|
242
280
|
}
|
|
243
281
|
this.bodyUsed = true;
|
|
244
282
|
try {
|
|
245
283
|
const parsed = await this.response.json();
|
|
246
284
|
this.jsonCache = parsed;
|
|
285
|
+
this.checkGraphQLErrors(parsed);
|
|
247
286
|
return parsed;
|
|
248
287
|
}
|
|
249
|
-
catch (
|
|
250
|
-
|
|
251
|
-
if (
|
|
252
|
-
throw
|
|
253
|
-
status: this.response.status,
|
|
254
|
-
response: this.response,
|
|
255
|
-
});
|
|
288
|
+
catch (error) {
|
|
289
|
+
// Re-throw RequestErrors from checkGraphQLErrors without wrapping
|
|
290
|
+
if (error instanceof RequestError) {
|
|
291
|
+
throw error;
|
|
256
292
|
}
|
|
257
|
-
|
|
293
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
294
|
+
throw new RequestError(`Invalid JSON: ${errorMessage}`, this.url || "", this.method || "", {
|
|
295
|
+
status: this.response.status,
|
|
296
|
+
response: this.response,
|
|
297
|
+
});
|
|
258
298
|
}
|
|
259
299
|
}
|
|
260
300
|
/**
|
|
@@ -263,7 +303,7 @@ class ResponseWrapper {
|
|
|
263
303
|
* if the body has already been consumed in those formats.
|
|
264
304
|
*
|
|
265
305
|
* @returns The response text
|
|
266
|
-
* @throws
|
|
306
|
+
* @throws {RequestError} When reading fails or the response has already been consumed in an incompatible format
|
|
267
307
|
*
|
|
268
308
|
* @example
|
|
269
309
|
* const text = await response.getText();
|
|
@@ -286,20 +326,16 @@ class ResponseWrapper {
|
|
|
286
326
|
}
|
|
287
327
|
catch (e) {
|
|
288
328
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
throw e;
|
|
329
|
+
throw new RequestError(`Cannot convert to text: ${errorMessage}`, this.url || "", this.method || "", {
|
|
330
|
+
status: this.response.status,
|
|
331
|
+
response: this.response,
|
|
332
|
+
});
|
|
295
333
|
}
|
|
296
334
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
throw new Error("Cannot convert to text");
|
|
335
|
+
throw new RequestError("Cannot convert to text", this.url || "", this.method || "", {
|
|
336
|
+
status: this.response.status,
|
|
337
|
+
response: this.response,
|
|
338
|
+
});
|
|
303
339
|
}
|
|
304
340
|
this.bodyUsed = true;
|
|
305
341
|
try {
|
|
@@ -308,13 +344,10 @@ class ResponseWrapper {
|
|
|
308
344
|
}
|
|
309
345
|
catch (e) {
|
|
310
346
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
throw e;
|
|
347
|
+
throw new RequestError(`Read failed: ${errorMessage}`, this.url || "", this.method || "", {
|
|
348
|
+
status: this.response.status,
|
|
349
|
+
response: this.response,
|
|
350
|
+
});
|
|
318
351
|
}
|
|
319
352
|
}
|
|
320
353
|
/**
|
|
@@ -323,7 +356,7 @@ class ResponseWrapper {
|
|
|
323
356
|
* if the body has already been consumed as text.
|
|
324
357
|
*
|
|
325
358
|
* @returns The response as a Blob
|
|
326
|
-
* @throws
|
|
359
|
+
* @throws {RequestError} When reading fails or the response has already been consumed in an incompatible format
|
|
327
360
|
*
|
|
328
361
|
* @example
|
|
329
362
|
* const blob = await response.getBlob();
|
|
@@ -339,12 +372,10 @@ class ResponseWrapper {
|
|
|
339
372
|
this.blobCache = new Blob([this.textCache], { type: "text/plain" });
|
|
340
373
|
return this.blobCache;
|
|
341
374
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
throw new Error("Cannot convert to Blob");
|
|
375
|
+
throw new RequestError("Cannot convert to Blob", this.url || "", this.method || "", {
|
|
376
|
+
status: this.response.status,
|
|
377
|
+
response: this.response,
|
|
378
|
+
});
|
|
348
379
|
}
|
|
349
380
|
this.bodyUsed = true;
|
|
350
381
|
try {
|
|
@@ -353,13 +384,10 @@ class ResponseWrapper {
|
|
|
353
384
|
}
|
|
354
385
|
catch (e) {
|
|
355
386
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
throw e;
|
|
387
|
+
throw new RequestError(`Read failed: ${errorMessage}`, this.url || "", this.method || "", {
|
|
388
|
+
status: this.response.status,
|
|
389
|
+
response: this.response,
|
|
390
|
+
});
|
|
363
391
|
}
|
|
364
392
|
}
|
|
365
393
|
/**
|
|
@@ -367,7 +395,7 @@ class ResponseWrapper {
|
|
|
367
395
|
* This method consumes the body and should only be called once.
|
|
368
396
|
*
|
|
369
397
|
* @returns The response body as a ReadableStream or null
|
|
370
|
-
* @throws
|
|
398
|
+
* @throws {RequestError} When the response body has already been consumed
|
|
371
399
|
*
|
|
372
400
|
* @example
|
|
373
401
|
* const stream = response.getBody();
|
|
@@ -378,12 +406,10 @@ class ResponseWrapper {
|
|
|
378
406
|
*/
|
|
379
407
|
getBody() {
|
|
380
408
|
if (this.bodyUsed) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
throw new Error("Body already consumed");
|
|
409
|
+
throw new RequestError("Body already consumed", this.url || "", this.method || "", {
|
|
410
|
+
status: this.response.status,
|
|
411
|
+
response: this.response,
|
|
412
|
+
});
|
|
387
413
|
}
|
|
388
414
|
this.bodyUsed = true;
|
|
389
415
|
return this.response.body;
|
|
@@ -394,6 +420,7 @@ class ResponseWrapper {
|
|
|
394
420
|
*
|
|
395
421
|
* @param selector - Optional function to extract and transform data
|
|
396
422
|
* @returns A promise that resolves to the selected data
|
|
423
|
+
* @throws {RequestError} When the request fails, JSON parsing fails, or the selector throws an error
|
|
397
424
|
*
|
|
398
425
|
* @example
|
|
399
426
|
* // Get full response
|
|
@@ -401,6 +428,16 @@ class ResponseWrapper {
|
|
|
401
428
|
*
|
|
402
429
|
* // Extract specific data
|
|
403
430
|
* const users = await response.getData(data => data.results.users);
|
|
431
|
+
*
|
|
432
|
+
* @example
|
|
433
|
+
* // Error handling - errors are always RequestError
|
|
434
|
+
* try {
|
|
435
|
+
* const data = await response.getData();
|
|
436
|
+
* } catch (error) {
|
|
437
|
+
* if (error instanceof RequestError) {
|
|
438
|
+
* console.log(error.status, error.url, error.method);
|
|
439
|
+
* }
|
|
440
|
+
* }
|
|
404
441
|
*/
|
|
405
442
|
async getData(selector) {
|
|
406
443
|
try {
|
|
@@ -419,20 +456,15 @@ class ResponseWrapper {
|
|
|
419
456
|
// Enhance selector errors with context
|
|
420
457
|
if (selector) {
|
|
421
458
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
throw new Error(`Data selector failed: ${errorMessage}`);
|
|
459
|
+
throw new RequestError(`Data selector failed: ${errorMessage}`, this.url || "", this.method || "", {
|
|
460
|
+
status: this.response.status,
|
|
461
|
+
response: this.response,
|
|
462
|
+
});
|
|
428
463
|
}
|
|
429
464
|
// If we get here and it's not a RequestError, wrap it
|
|
430
465
|
// This should rarely happen as getJson() should throw RequestError
|
|
431
466
|
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
432
|
-
|
|
433
|
-
throw RequestError.networkError(this.url, this.method, errorObj);
|
|
434
|
-
}
|
|
435
|
-
throw errorObj;
|
|
467
|
+
throw RequestError.networkError(this.url || "", this.method || "", errorObj);
|
|
436
468
|
}
|
|
437
469
|
}
|
|
438
470
|
}
|
|
@@ -840,6 +872,13 @@ class BaseRequest {
|
|
|
840
872
|
this.errorInterceptors = [];
|
|
841
873
|
this.url = url;
|
|
842
874
|
}
|
|
875
|
+
/**
|
|
876
|
+
* Get GraphQL options if set (only for BodyRequest subclasses)
|
|
877
|
+
* @returns GraphQL options or undefined
|
|
878
|
+
*/
|
|
879
|
+
getGraphQLOptions() {
|
|
880
|
+
return undefined;
|
|
881
|
+
}
|
|
843
882
|
/**
|
|
844
883
|
* Creates a fluent API for setting enum-based options
|
|
845
884
|
* Combines direct setter with convenience methods
|
|
@@ -925,14 +964,14 @@ class BaseRequest {
|
|
|
925
964
|
*
|
|
926
965
|
* @param timeout - The timeout in milliseconds
|
|
927
966
|
* @returns The request instance for chaining
|
|
928
|
-
* @throws
|
|
967
|
+
* @throws RequestError if timeout is not a positive number
|
|
929
968
|
*
|
|
930
969
|
* @example
|
|
931
970
|
* request.withTimeout(5000); // 5 seconds timeout
|
|
932
971
|
*/
|
|
933
972
|
withTimeout(timeout) {
|
|
934
973
|
if (!Number.isFinite(timeout) || timeout <= 0)
|
|
935
|
-
throw new
|
|
974
|
+
throw new RequestError("Timeout must be a positive number", this.url, this.method);
|
|
936
975
|
this.requestOptions.timeout = timeout;
|
|
937
976
|
return this;
|
|
938
977
|
}
|
|
@@ -941,14 +980,14 @@ class BaseRequest {
|
|
|
941
980
|
*
|
|
942
981
|
* @param retries - Number of retry attempts before failing
|
|
943
982
|
* @returns The request instance for chaining
|
|
944
|
-
* @throws
|
|
983
|
+
* @throws RequestError if retries is not a non-negative integer
|
|
945
984
|
*
|
|
946
985
|
* @example
|
|
947
986
|
* request.withRetries(3); // Retry up to 3 times
|
|
948
987
|
*/
|
|
949
988
|
withRetries(retries) {
|
|
950
989
|
if (!Number.isInteger(retries) || retries < 0)
|
|
951
|
-
throw new
|
|
990
|
+
throw new RequestError("Retry count must be a non-negative integer", this.url, this.method);
|
|
952
991
|
this.requestOptions.retries = retries;
|
|
953
992
|
return this;
|
|
954
993
|
}
|
|
@@ -1155,7 +1194,7 @@ class BaseRequest {
|
|
|
1155
1194
|
if (typeof Buffer !== "undefined")
|
|
1156
1195
|
return Buffer.from(str).toString("base64");
|
|
1157
1196
|
// Fallback (should never happen in modern environments)
|
|
1158
|
-
throw new
|
|
1197
|
+
throw new RequestError("Base64 encoding is not supported", this.url, this.method);
|
|
1159
1198
|
}
|
|
1160
1199
|
/**
|
|
1161
1200
|
* Sets the bearer token for authentication
|
|
@@ -1336,9 +1375,20 @@ class BaseRequest {
|
|
|
1336
1375
|
* Execute the request and parse the response as JSON
|
|
1337
1376
|
*
|
|
1338
1377
|
* @returns A promise that resolves to the parsed JSON data
|
|
1378
|
+
* @throws {RequestError} When the request fails, JSON parsing fails, GraphQL errors occur (if throwOnError enabled), or body is already consumed
|
|
1339
1379
|
*
|
|
1340
1380
|
* @example
|
|
1341
1381
|
* const users = await request.getJson<User[]>();
|
|
1382
|
+
*
|
|
1383
|
+
* @example
|
|
1384
|
+
* // Error handling - errors are always RequestError
|
|
1385
|
+
* try {
|
|
1386
|
+
* const data = await request.getJson();
|
|
1387
|
+
* } catch (error) {
|
|
1388
|
+
* if (error instanceof RequestError) {
|
|
1389
|
+
* console.log(error.status, error.url, error.method);
|
|
1390
|
+
* }
|
|
1391
|
+
* }
|
|
1342
1392
|
*/
|
|
1343
1393
|
async getJson() {
|
|
1344
1394
|
const response = await this.getResponse();
|
|
@@ -1386,6 +1436,7 @@ class BaseRequest {
|
|
|
1386
1436
|
*
|
|
1387
1437
|
* @param selector - Optional function to extract and transform data
|
|
1388
1438
|
* @returns A promise that resolves to the selected data
|
|
1439
|
+
* @throws {RequestError} When the request fails, JSON parsing fails, or the selector throws an error
|
|
1389
1440
|
*
|
|
1390
1441
|
* @example
|
|
1391
1442
|
* // Get full response
|
|
@@ -1393,6 +1444,16 @@ class BaseRequest {
|
|
|
1393
1444
|
*
|
|
1394
1445
|
* // Extract specific data
|
|
1395
1446
|
* const users = await request.getData(data => data.results.users);
|
|
1447
|
+
*
|
|
1448
|
+
* @example
|
|
1449
|
+
* // Error handling - errors are always RequestError
|
|
1450
|
+
* try {
|
|
1451
|
+
* const data = await request.getData();
|
|
1452
|
+
* } catch (error) {
|
|
1453
|
+
* if (error instanceof RequestError) {
|
|
1454
|
+
* console.log(error.status, error.url, error.method);
|
|
1455
|
+
* }
|
|
1456
|
+
* }
|
|
1396
1457
|
*/
|
|
1397
1458
|
async getData(selector) {
|
|
1398
1459
|
const response = await this.getResponse();
|
|
@@ -1442,7 +1503,8 @@ class BaseRequest {
|
|
|
1442
1503
|
* @returns The URL with query parameters appended
|
|
1443
1504
|
*/
|
|
1444
1505
|
formatUrlWithQueryParams(url) {
|
|
1445
|
-
|
|
1506
|
+
const queryString = this.queryParams.toString();
|
|
1507
|
+
if (!queryString) {
|
|
1446
1508
|
return url;
|
|
1447
1509
|
}
|
|
1448
1510
|
try {
|
|
@@ -1458,7 +1520,7 @@ class BaseRequest {
|
|
|
1458
1520
|
// Handle relative URLs
|
|
1459
1521
|
const hasExistingParams = url.includes("?");
|
|
1460
1522
|
const separator = hasExistingParams ? "&" : "?";
|
|
1461
|
-
return `${url}${separator}${
|
|
1523
|
+
return `${url}${separator}${queryString}`;
|
|
1462
1524
|
}
|
|
1463
1525
|
}
|
|
1464
1526
|
/**
|
|
@@ -1507,8 +1569,7 @@ class BaseRequest {
|
|
|
1507
1569
|
}
|
|
1508
1570
|
catch (error) {
|
|
1509
1571
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1510
|
-
|
|
1511
|
-
throw new RequestError(`Interceptor failed: ${errorMessage}`, currentConfig.url, currentConfig.method);
|
|
1572
|
+
throw new RequestError(`Request interceptor failed: ${errorMessage}`, currentConfig.url, currentConfig.method);
|
|
1512
1573
|
}
|
|
1513
1574
|
}
|
|
1514
1575
|
return currentConfig;
|
|
@@ -1530,12 +1591,9 @@ class BaseRequest {
|
|
|
1530
1591
|
}
|
|
1531
1592
|
catch (error) {
|
|
1532
1593
|
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}`);
|
|
1594
|
+
const url = currentResponse.url || "";
|
|
1595
|
+
const method = currentResponse.method || "";
|
|
1596
|
+
throw new RequestError(`Response interceptor failed: ${errorMessage}`, url, method);
|
|
1539
1597
|
}
|
|
1540
1598
|
}
|
|
1541
1599
|
return currentResponse;
|
|
@@ -1569,11 +1627,16 @@ class BaseRequest {
|
|
|
1569
1627
|
const errorMessage = interceptorError instanceof Error ? interceptorError.message : String(interceptorError);
|
|
1570
1628
|
// Always wrap in RequestError when we have context
|
|
1571
1629
|
if (currentError instanceof RequestError) {
|
|
1572
|
-
currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method
|
|
1630
|
+
currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method, {
|
|
1631
|
+
status: currentError.status,
|
|
1632
|
+
response: currentError.response,
|
|
1633
|
+
});
|
|
1573
1634
|
}
|
|
1574
1635
|
else if (currentError instanceof ResponseWrapper && currentError.url && currentError.method) {
|
|
1575
1636
|
// 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
|
|
1637
|
+
currentError = new RequestError(`Error interceptor ${i + 1} failed: ${errorMessage}`, currentError.url, currentError.method, {
|
|
1638
|
+
status: currentError.status,
|
|
1639
|
+
});
|
|
1577
1640
|
}
|
|
1578
1641
|
else {
|
|
1579
1642
|
// Last resort: if we have no context at all, use the original error's context
|
|
@@ -1695,7 +1758,8 @@ class BaseRequest {
|
|
|
1695
1758
|
const interceptorResult = await this.runRequestInterceptors(requestConfig);
|
|
1696
1759
|
// If interceptor returned a Response, short-circuit and wrap it
|
|
1697
1760
|
if (interceptorResult instanceof Response) {
|
|
1698
|
-
const
|
|
1761
|
+
const graphQLOptions = this.getGraphQLOptions();
|
|
1762
|
+
const wrappedResponse = new ResponseWrapper(interceptorResult, this.url, this.method, graphQLOptions);
|
|
1699
1763
|
return await this.runResponseInterceptors(wrappedResponse);
|
|
1700
1764
|
}
|
|
1701
1765
|
// Update fetchOptions with interceptor modifications
|
|
@@ -1731,7 +1795,8 @@ class BaseRequest {
|
|
|
1731
1795
|
if (!response.ok) {
|
|
1732
1796
|
throw RequestError.fromResponse(response, url, method);
|
|
1733
1797
|
}
|
|
1734
|
-
const
|
|
1798
|
+
const graphQLOptions = this.getGraphQLOptions();
|
|
1799
|
+
const wrappedResponse = new ResponseWrapper(response, url, method, graphQLOptions);
|
|
1735
1800
|
return await this.runResponseInterceptors(wrappedResponse);
|
|
1736
1801
|
}
|
|
1737
1802
|
catch (error) {
|
|
@@ -1764,6 +1829,7 @@ class BaseRequest {
|
|
|
1764
1829
|
class BodyRequest extends BaseRequest {
|
|
1765
1830
|
constructor(url) {
|
|
1766
1831
|
super(url);
|
|
1832
|
+
this.graphQLOptions = undefined;
|
|
1767
1833
|
}
|
|
1768
1834
|
/**
|
|
1769
1835
|
* Sets the body of the request
|
|
@@ -1786,7 +1852,8 @@ class BodyRequest extends BaseRequest {
|
|
|
1786
1852
|
JSON.stringify(body);
|
|
1787
1853
|
}
|
|
1788
1854
|
catch (error) {
|
|
1789
|
-
|
|
1855
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1856
|
+
throw new RequestError(`JSON stringify failed: ${errorMessage}`, this.url, this.method);
|
|
1790
1857
|
}
|
|
1791
1858
|
}
|
|
1792
1859
|
else {
|
|
@@ -1800,6 +1867,7 @@ class BodyRequest extends BaseRequest {
|
|
|
1800
1867
|
*
|
|
1801
1868
|
* @param query - The GraphQL query or mutation string
|
|
1802
1869
|
* @param variables - Optional variables object to pass with the query
|
|
1870
|
+
* @param options - Optional GraphQL-specific options
|
|
1803
1871
|
* @returns The request instance for chaining
|
|
1804
1872
|
*
|
|
1805
1873
|
* @example
|
|
@@ -1809,13 +1877,18 @@ class BodyRequest extends BaseRequest {
|
|
|
1809
1877
|
* @example
|
|
1810
1878
|
* const request = new PostRequest('/graphql')
|
|
1811
1879
|
* .withGraphQL('mutation { createUser(name: $name) { id } }', { name: 'John' });
|
|
1880
|
+
*
|
|
1881
|
+
* @example
|
|
1882
|
+
* // Throw an error if the GraphQL response contains errors
|
|
1883
|
+
* const request = new PostRequest('/graphql')
|
|
1884
|
+
* .withGraphQL('query { user { id } }', undefined, { throwOnError: true });
|
|
1812
1885
|
*/
|
|
1813
|
-
withGraphQL(query, variables) {
|
|
1814
|
-
if (typeof query !== "string" || query.
|
|
1886
|
+
withGraphQL(query, variables, options) {
|
|
1887
|
+
if (typeof query !== "string" || query.length === 0) {
|
|
1815
1888
|
throw new RequestError("Invalid GraphQL query", this.url, this.method);
|
|
1816
1889
|
}
|
|
1817
1890
|
const graphQLBody = {
|
|
1818
|
-
query: query
|
|
1891
|
+
query: query,
|
|
1819
1892
|
};
|
|
1820
1893
|
if (variables !== undefined) {
|
|
1821
1894
|
if (typeof variables !== "object" || variables === null || Array.isArray(variables)) {
|
|
@@ -1823,12 +1896,24 @@ class BodyRequest extends BaseRequest {
|
|
|
1823
1896
|
}
|
|
1824
1897
|
graphQLBody.variables = variables;
|
|
1825
1898
|
}
|
|
1899
|
+
// Store GraphQL options if provided
|
|
1900
|
+
if (options !== undefined) {
|
|
1901
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
1902
|
+
throw new RequestError("Invalid GraphQL options", this.url, this.method);
|
|
1903
|
+
}
|
|
1904
|
+
// Store only the known GraphQL options properties
|
|
1905
|
+
const opts = options;
|
|
1906
|
+
this.graphQLOptions = {
|
|
1907
|
+
throwOnError: typeof opts.throwOnError === "boolean" ? opts.throwOnError : undefined,
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1826
1910
|
// Validate JSON is stringifiable early
|
|
1827
1911
|
try {
|
|
1828
1912
|
JSON.stringify(graphQLBody);
|
|
1829
1913
|
}
|
|
1830
1914
|
catch (error) {
|
|
1831
|
-
|
|
1915
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1916
|
+
throw new RequestError(`JSON stringify failed: ${errorMessage}`, this.url, this.method);
|
|
1832
1917
|
}
|
|
1833
1918
|
this.body = graphQLBody;
|
|
1834
1919
|
this.bodyType = BodyType.JSON;
|
|
@@ -1851,6 +1936,13 @@ class BodyRequest extends BaseRequest {
|
|
|
1851
1936
|
this.withContentType(contentType);
|
|
1852
1937
|
}
|
|
1853
1938
|
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Get the GraphQL options if set
|
|
1941
|
+
* @returns The GraphQL options or undefined
|
|
1942
|
+
*/
|
|
1943
|
+
getGraphQLOptions() {
|
|
1944
|
+
return this.graphQLOptions;
|
|
1945
|
+
}
|
|
1854
1946
|
/**
|
|
1855
1947
|
* Execute the request and return the ResponseWrapper
|
|
1856
1948
|
* Overrides the base implementation to add body handling
|