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